Add OIDC login and callback against Authentik
internal/auth discovers the provider at MEALQUEST_OIDC_ISSUER with coreos/go-oidc/v3 and runs a confidential-client authorization code flow with golang.org/x/oauth2 (client_secret_basic). GET /auth/login stores state, nonce and a PKCE S256 verifier in a 10-minute HMAC-signed HttpOnly __Host- cookie and redirects with scopes "openid profile email groups offline_access". An optional "return" path must be same-origin; anything else fails the login with 400. GET /auth/callback clears the single-use cookie, checks state, exchanges the code with the verifier, verifies the ID token (RS256 only, issuer, audience, expiry) and nonce, then hands the claims to a SessionStarter. cmd/server wires a placeholder that refuses every login until the session layer lands. Config gains required MEALQUEST_OIDC_ISSUER, MEALQUEST_OIDC_CLIENT_ID, MEALQUEST_OIDC_CLIENT_SECRET_FILE and MEALQUEST_OIDC_ALLOWED_GROUPS. Pins go-oidc v3.21.0 and oauth2 v0.36.0, the newest releases that support Go 1.25 (oauth2 v0.37.0 requires Go 1.26). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -25,13 +27,39 @@ func newTestRouter(t *testing.T, version string) http.Handler {
|
||||
"index.html": {Data: []byte(fixtureIndex)},
|
||||
"assets/index-AbC123.js": {Data: []byte(fixtureAsset)},
|
||||
}
|
||||
r, err := NewRouter(logger, version, assets)
|
||||
r, err := NewRouter(logger, version, stubAuth{}, assets)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// stubAuth mounts a fixed GET /auth/login in place of the real auth flow.
|
||||
type stubAuth struct{}
|
||||
|
||||
func (stubAuth) Mount(r chi.Router) {
|
||||
r.Get("/auth/login", func(w http.ResponseWriter, _ *http.Request) {
|
||||
io.WriteString(w, "stub login")
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthRoutesMounted(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
router := newTestRouter(t, "dev")
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/auth/login", nil))
|
||||
if rec.Code != http.StatusOK || rec.Body.String() != "stub login" {
|
||||
t.Errorf("GET /auth/login = %d %q, want the mounted handler", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
rec = httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, httptest.NewRequest(http.MethodPost, "/auth/login", nil))
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("POST /auth/login status = %d, want 405", rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealth(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -99,7 +127,7 @@ func TestReservedPathsNotFound(t *testing.T) {
|
||||
{http.MethodGet, "/api"},
|
||||
{http.MethodGet, "/api/unknown"},
|
||||
{http.MethodPost, "/api/unknown"},
|
||||
{http.MethodGet, "/auth/callback"},
|
||||
{http.MethodGet, "/auth/unknown"},
|
||||
{http.MethodGet, "/media/photo.jpg"},
|
||||
{http.MethodGet, "/health/deep"},
|
||||
{http.MethodGet, "/auth/../quests/42"},
|
||||
|
||||
Reference in New Issue
Block a user