mirror of
https://github.com/SigNoz/signoz.git
synced 2026-08-14 00:40:35 +01:00
#### Description
- Moves the endpoints to `/api/v2/auth_domains` and removes the
`/api/v1/domains` routes — the request/response shapes changed, so they
live behind new paths instead of breaking v1 in place.
- Restructures the auth domain payload: `config` is now a `{kind, spec}`
discriminated envelope (same pattern as `RuleThresholdData` /
`EvaluationEnvelope`), replacing the old `ssoType` discriminator with
`samlConfig` / `googleAuthConfig` / `oidcConfig` sibling fields;
`ssoEnabled` and `roleMapping` move to the root as `enabled` and
`roleMapping`.
- Renames the provider kind `google_auth` → `google`, and the SAML keys
to metadata-consistent ones: `samlEntity` → `entityId`, `samlIdp` →
`location`, `samlCert` → `certificate`.
- Migrates the persisted documents too: a new sqlmigration rewrites
`auth_domain.data` into `{enabled, config: {kind, spec}, roleMapping}`,
so all legacy-shape code (storable twins, `google_auth` translation,
per-kind conversion switches) is deleted; the remaining per-kind wiring
lives in a single variant registry that `UnmarshalJSON`,
`JSONSchemaOneOf` and the discriminator mapping derive from.
- `AuthDomain` exposes the domain shape (`Enabled()`, `Kind()`,
`Config()`, `RoleMapping()`, typed spec accessors) instead of the
persisted document; `config` presence is enforced explicitly on
Postable/Updatable (the old PUT path never enforced it and could poison
a row).
- Secret fields (`clientSecret`, `serviceAccountJson`) are `format:
password` in the schema, and `GoogleConfig` loses the unused
`redirectURI` (the migration strips it from persisted documents).
- Frontend: regenerated client is a clean discriminated union; both
directions of the envelope↔form translation live in
`CreateEdit.utils.ts` with an explicit kind→provider mapping (no
cross-enum casts).
- The generated OpenAPI spec carries a real `discriminator`; the
kind/spec envelope pattern itself is documented generically in #12494,
and this PR only keeps the auth domain worked example in `types.md` in
step with the refactored types.
- Updates the google authn integration tests (#12486) to the new API,
and adds parametrized POST→GET roundtrip cases pinning the response
contract per kind (server-side defaulting, role-name normalization, null
maps) plus enforcement-toggle update coverage.
#### Issues closed by this PR
Closes SigNoz/platform-pod#2268
#### Additional Information
- Breaking change: `/api/v1/domains` is gone; the resource is now
`/api/v2/auth_domains` with the new shape. Login and SSO callback flows
are behaviorally unchanged, and existing rows are migrated in place at
startup.
- The `AuthNProvider` rename also surfaces in `/api/v2/sessions/context`
responses (`provider: "google"`) — the login page only consumes the
callback `url` — and in the reported stats key, which changes from
`authdomain.google_auth.count` to `authdomain.google.count`.
- Verified: `make go-test`, Go lint, frontend jest suites for
AuthDomain, `pnpm build`, `pnpm tsgo --noEmit`, and the full
`callbackauthn` domain suites (17 tests: roundtrip pins, the enforcement
toggle, and the google E2E flows) against a container rebuilt from this
branch — including a live run of the data migration over legacy-format
rows.
174 lines
4.7 KiB
Go
174 lines
4.7 KiB
Go
package implsession
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
"github.com/SigNoz/signoz/pkg/global"
|
|
"github.com/SigNoz/signoz/pkg/http/binding"
|
|
"github.com/SigNoz/signoz/pkg/http/render"
|
|
"github.com/SigNoz/signoz/pkg/modules/session"
|
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
)
|
|
|
|
type handler struct {
|
|
module session.Module
|
|
globalConfig global.Config
|
|
}
|
|
|
|
func NewHandler(module session.Module, globalConfig global.Config) session.Handler {
|
|
return &handler{module: module, globalConfig: globalConfig}
|
|
}
|
|
|
|
func (handler *handler) GetSessionContext(rw http.ResponseWriter, req *http.Request) {
|
|
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
email, err := valuer.NewEmail(req.URL.Query().Get("email"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
siteURL, err := url.Parse(req.URL.Query().Get("ref"))
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
sessionContext, err := handler.module.GetSessionContext(ctx, email, siteURL)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, sessionContext)
|
|
}
|
|
|
|
func (handler *handler) CreateSessionByEmailPassword(rw http.ResponseWriter, req *http.Request) {
|
|
ctx, cancel := context.WithTimeout(req.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
body := new(authtypes.PostableEmailPasswordSession)
|
|
if err := binding.JSON.BindBody(req.Body, body); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
token, err := handler.module.CreatePasswordAuthNSession(ctx, authtypes.AuthNProviderEmailPassword, body.Email, body.Password, body.OrgID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, authtypes.NewGettableTokenFromToken(token, handler.module.GetRotationInterval(ctx)))
|
|
}
|
|
|
|
func (handler *handler) CreateSessionByGoogleCallback(rw http.ResponseWriter, req *http.Request) {
|
|
ctx, cancel := context.WithTimeout(req.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
values := req.URL.Query()
|
|
|
|
redirectURL, err := handler.module.CreateCallbackAuthNSession(ctx, authtypes.AuthNProviderGoogle, values)
|
|
if err != nil {
|
|
http.Redirect(rw, req, handler.getRedirectURLFromErr(err), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
http.Redirect(rw, req, redirectURL, http.StatusSeeOther)
|
|
}
|
|
|
|
func (handler *handler) CreateSessionBySAMLCallback(rw http.ResponseWriter, req *http.Request) {
|
|
ctx, cancel := context.WithTimeout(req.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
err := req.ParseForm()
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
redirectURL, err := handler.module.CreateCallbackAuthNSession(ctx, authtypes.AuthNProviderSAML, req.Form)
|
|
if err != nil {
|
|
http.Redirect(rw, req, handler.getRedirectURLFromErr(err), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
http.Redirect(rw, req, redirectURL, http.StatusSeeOther)
|
|
}
|
|
|
|
func (handler *handler) CreateSessionByOIDCCallback(rw http.ResponseWriter, req *http.Request) {
|
|
ctx, cancel := context.WithTimeout(req.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
values := req.URL.Query()
|
|
redirectURL, err := handler.module.CreateCallbackAuthNSession(ctx, authtypes.AuthNProviderOIDC, values)
|
|
if err != nil {
|
|
http.Redirect(rw, req, handler.getRedirectURLFromErr(err), http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
http.Redirect(rw, req, redirectURL, http.StatusSeeOther)
|
|
}
|
|
|
|
func (handler *handler) RotateSession(rw http.ResponseWriter, req *http.Request) {
|
|
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
body := new(authtypes.PostableRotateToken)
|
|
if err := binding.JSON.BindBody(req.Body, body); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
accessToken, err := authtypes.AccessTokenFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
token, err := handler.module.RotateSession(ctx, accessToken, body.RefreshToken)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, authtypes.NewGettableTokenFromToken(token, handler.module.GetRotationInterval(ctx)))
|
|
}
|
|
|
|
func (handler *handler) DeleteSession(rw http.ResponseWriter, req *http.Request) {
|
|
ctx, cancel := context.WithTimeout(req.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
accessToken, err := authtypes.AccessTokenFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
err = handler.module.DeleteSession(ctx, accessToken)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
func (handler *handler) getRedirectURLFromErr(err error) string {
|
|
values := errors.AsURLValues(err)
|
|
values.Add("callbackauthnerr", "true")
|
|
|
|
return (&url.URL{
|
|
// When UI is being served on a prefix, we need to redirect to the login page on the prefix.
|
|
Path: path.Join(handler.globalConfig.ExternalPath(), "/login"),
|
|
RawQuery: values.Encode(),
|
|
}).String()
|
|
}
|