From 1fec7b66d162c83d46d8eda9be4f6326148d31ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= Date: Thu, 24 Sep 2026 00:13:28 +0200 Subject: [PATCH] feat: add email scope to OAuth configuration and update documentation --- server/internal/config/member.go | 2 +- server/internal/member/oauth/service.go | 6 ++++++ webpage/docs/configuration/authentication.md | 6 +++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/server/internal/config/member.go b/server/internal/config/member.go index c37c5f2e..29f62b30 100644 --- a/server/internal/config/member.go +++ b/server/internal/config/member.go @@ -152,7 +152,7 @@ func (Member) Init(cmd *cobra.Command) error { return err } - cmd.PersistentFlags().StringSlice("member.oauth.scopes", []string{"openid", "profile"}, "OAuth 2.0 scopes") + cmd.PersistentFlags().StringSlice("member.oauth.scopes", []string{"openid", "profile", "email"}, "OAuth 2.0 scopes") if err := viper.BindPFlag("member.oauth.scopes", cmd.PersistentFlags().Lookup("member.oauth.scopes")); err != nil { return err } diff --git a/server/internal/member/oauth/service.go b/server/internal/member/oauth/service.go index 2c7b5e2a..f6d2b4bc 100644 --- a/server/internal/member/oauth/service.go +++ b/server/internal/member/oauth/service.go @@ -13,6 +13,8 @@ import ( "sync" "time" + "github.com/rs/zerolog/log" + "github.com/m1k1o/neko/server/pkg/types" ) @@ -166,6 +168,10 @@ func (service *Service) Complete(ctx context.Context, stateToken, code string) ( email = claim(idClaims, "email") } + if email == "" && (len(config.AdminEmails) > 0 || len(config.UserEmails) > 0) { + log.Warn().Msg("OAuth admin_emails/user_emails are configured but the provider did not return an 'email' claim; request the 'email' scope") + } + isAdmin := boolClaim(claims, "isAdmin") || boolClaim(idClaims, "isAdmin") admin := administrator(email, isAdmin, config.AdminEmails) diff --git a/webpage/docs/configuration/authentication.md b/webpage/docs/configuration/authentication.md index addc85c5..f5922245 100644 --- a/webpage/docs/configuration/authentication.md +++ b/webpage/docs/configuration/authentication.md @@ -196,7 +196,7 @@ member: client_id: "" client_secret: "" issuer_url: "https://id.example.com" - scopes: ["openid", "profile"] + scopes: ["openid", "profile", "email"] # Configure these to match the JSON returned by userinfo_url. subject_field: "sub" username_field: "name" @@ -230,6 +230,10 @@ The sign-in endpoint is `GET /api/oauth/login`; the callback endpoint is `GET /a `user_emails` restricts non-admin login to a list of allowed emails, matched the same way as `admin_emails`. If `user_emails` is empty (the default), any authenticated user is allowed to log in. Administrators (matched by `admin_emails` or `isAdmin: true`) can always log in regardless of `user_emails`. Users whose email is not allowed receive an HTTP 403 response and no session is created. +:::warning +`admin_emails` and `user_emails` are matched against the `email` claim. Most providers only include that claim if the `email` scope is requested, so `scopes` must include `"email"` (in addition to `"openid"`) or the claim will be empty and these lists will never match. +::: + For OAuth sessions, `GET /api/whoami` includes `extra_data`: the merged userinfo and ID-token claims, with userinfo values taking precedence. It is returned only to the authenticated session owner and is intended for profile-field troubleshooting. Do not place secrets in provider profile claims. For non-OIDC providers, set `authorization_url`, `token_url`, `userinfo_url`, and optionally `redirect_url` directly. For GitHub, for example, use `read:user` as a scope and set `subject_field: id`, `username_field: login`, and `avatar_field: avatar_url`.