Compare commits

...
Author SHA1 Message Date
Stavros 6fe21a7e38 fix: rabbit comments 2026-08-27 17:53:38 +03:00
Stavros 0da39c93b0 tests: add tests for skip consent 2026-08-27 17:22:15 +03:00
Stavros 766a7287e3 fix: consent screen skip status should get checked after auth 2026-08-27 17:08:08 +03:00
Stavros 4bb766997b chore: remove dosu from sponsors 2026-08-25 17:16:50 +03:00
7 changed files with 436 additions and 107 deletions
-1
View File
@@ -81,7 +81,6 @@ A huge thank you to [selfh.st](https://selfh.st) for their generous donation to
- [JetBrains for Open-Source](https://jb.gg/OpenSource)
- [CodeRabbit AI](https://www.coderabbit.ai)
- [Dosu](https://dosu.dev/)
- [InstaPods](https://instapods.com) - [Deploy Tinyauth from 3$/month](https://app.instapods.com/dashboard/pods/create?app=tinyauth&ref=tinyauth)
## Acknowledgements
+39 -16
View File
@@ -1,5 +1,5 @@
import { useUserContext } from "@/context/user-context";
import { useMutation } from "@tanstack/react-query";
import {useMutation} from "@tanstack/react-query";
import { Navigate, useNavigate } from "react-router";
import { useLocation } from "react-router";
import {
@@ -25,7 +25,8 @@ import {
searchParamsFromObject,
useScreenParams,
} from "@/lib/hooks/screen-params";
import { useEffect } from "react";
import {useEffect, useState} from "react";
import { z } from "zod";
type Scope = {
id: string;
@@ -34,6 +35,10 @@ type Scope = {
icon: React.ReactNode;
};
const skipConsentResponseSchema = z.object({
skipConsent: z.boolean(),
})
const scopeMapIconProps = {
className: "stroke-muted-foreground stroke-[1.75] h-4",
};
@@ -96,14 +101,8 @@ export const AuthorizePage = () => {
}
return "";
})();
// TODO: maybe a better way to do this
const shouldAutoAuthorize =
auth.authenticated &&
isOidc &&
screenParams.oidc_ticket !== undefined &&
screenParams.oidc_scope !== undefined &&
screenParams.oidc_prompt === "none";
const [autoAuthorize, setAutoAuthorize] = useState(false);
const [skipConsentChecked, setSkipConsentChecked] = useState(false);
const { mutate: authorizeMutate, isPending: authorizePending } = useMutation({
mutationFn: () => {
@@ -126,10 +125,34 @@ export const AuthorizePage = () => {
});
useEffect(() => {
if (shouldAutoAuthorize) {
authorizeMutate();
}
}, [shouldAutoAuthorize, authorizeMutate]);
let active = true;
const controller = new AbortController();
const checkSkipConsent = async () => {
try {
const res = await fetch(
`/api/oidc/skip-consent?oidc_ticket=${encodeURIComponent( screenParams.oidc_ticket ?? "")}`,
{ signal: controller.signal },
);
if (!res.ok) return;
const parsed = skipConsentResponseSchema.safeParse(await res.json());
if (!active || !parsed.success || !parsed.data.skipConsent) return;
setAutoAuthorize(true);
authorizeMutate();
} catch {
// Fall back to manual consent on any failure (including abort).
} finally {
if (active) setSkipConsentChecked(true);
}
};
checkSkipConsent();
return () => {
active = false;
controller.abort();
};
}, [authorizeMutate, screenParams.oidc_ticket]);
if (!isOidc || !screenParams.oidc_ticket || !screenParams.oidc_scope) {
return (
@@ -190,13 +213,13 @@ export const AuthorizePage = () => {
<CardFooter className="flex flex-col items-stretch gap-3">
<Button
onClick={() => authorizeMutate()}
loading={authorizePending || shouldAutoAuthorize}
loading={authorizePending || autoAuthorize || !skipConsentChecked}
>
{t("authorizeTitle")}
</Button>
<Button
onClick={() => navigate(`/logout${compiledParams}`)}
disabled={authorizePending || shouldAutoAuthorize}
disabled={authorizePending || autoAuthorize}
variant="outline"
>
{t("cancelTitle")}
+1 -1
View File
@@ -9,7 +9,7 @@ export default defineConfig({
plugins: [react(), tailwindcss(), visualizer()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@": path.resolve(import.meta.dirname, "./src"),
},
},
build: {
+5
View File
@@ -18,3 +18,8 @@ type RedirectQuery struct {
RedirectURI string `url:"redirect_uri"`
LoginFor FrontendLoginFor `url:"login_for"`
}
type SimpleResponse struct {
Status int `json:"status"`
Message string `json:"message"`
}
+96 -17
View File
@@ -62,15 +62,19 @@ type ErrorScreen struct {
Error string `url:"error"`
}
type ClientRequest struct {
ClientID string `uri:"id" binding:"required"`
}
type ClientCredentials struct {
ClientID string
ClientSecret string
}
type SkipConsentRequest struct {
OIDCTicket string `form:"oidc_ticket" binding:"required"`
}
type SkipConsentResponse struct {
SkipConsent bool `json:"skipConsent"`
}
type AuthorizeScreenParams struct {
LoginFor FrontendLoginFor `url:"login_for"`
OIDCTicket string `url:"oidc_ticket"`
@@ -105,6 +109,7 @@ func NewOIDCController(i OIDCControllerInput) *OIDCController {
oidcGroup := i.RouterGroup.Group("/oidc")
oidcGroup.POST("/authorize-complete", controller.authorizeComplete)
oidcGroup.GET("/skip-consent", controller.skipConsent)
oidcGroup.POST("/token", controller.Token)
oidcGroup.GET("/userinfo", controller.Userinfo)
oidcGroup.POST("/userinfo", controller.Userinfo)
@@ -242,16 +247,6 @@ func (controller *OIDCController) authorize(c *gin.Context) {
}
}
if userContext != nil && userContext.Authenticated && values.OIDCPrompt != service.OIDCPromptLogin {
consent, err := controller.oidc.GetOIDCConsent(c, userContext.GetUsername(), req.ClientID)
if err != nil {
controller.log.App.Warn().Err(err).Msg("Failed to get OIDC consent")
} else if consent != nil && scopesGranted(consent.Scope, req.Scope) {
values.OIDCPrompt = service.OIDCPromptNone
}
}
queries, err := query.Values(values)
if err != nil {
@@ -270,6 +265,90 @@ func (controller *OIDCController) authorize(c *gin.Context) {
c.Redirect(http.StatusFound, redirectUrl)
}
func (controller *OIDCController) skipConsent(c *gin.Context) {
c.Header("cache-control", "no-store")
c.Header("pragma", "no-cache")
if controller.oidc == nil {
c.JSON(500, SimpleResponse{
Status: 500,
Message: "OIDC not configured",
})
return
}
userContext, err := new(model.UserContext).NewFromGin(c)
if err != nil {
if !errors.Is(err, model.ErrUserContextNotFound) {
controller.log.App.Warn().Err(err).Msg("Failed to get user context")
}
}
if err != nil || !userContext.Authenticated {
c.JSON(401, SimpleResponse{
Status: 401,
Message: "User not logged in",
})
return
}
var req SkipConsentRequest
err = c.BindQuery(&req)
if err != nil {
c.JSON(400, SimpleResponse{
Status: 400,
Message: "Bad request",
})
return
}
controller.log.App.Debug().Interface("req", req).Msg("Received skip consent request")
authorizeReq, ok := controller.oidc.GetAuthorizeRequestByTicket(req.OIDCTicket)
if !ok {
c.JSON(200, SkipConsentResponse{
SkipConsent: false,
})
return
}
controller.log.App.Debug().Str("client", authorizeReq.ClientID).Str("user", userContext.GetUsername()).Msg("User consented to OIDC")
if authorizeReq.Prompt == service.OIDCPromptLogin.String() {
c.JSON(200, SkipConsentResponse{
SkipConsent: false,
})
return
}
consent, err := controller.oidc.GetOIDCConsent(c, userContext.GetUsername(), authorizeReq.ClientID)
if err != nil || consent == nil {
if err != nil {
controller.log.App.Warn().Err(err).Msg("Failed to get OIDC consent")
}
c.JSON(200, SkipConsentResponse{
SkipConsent: false,
})
return
}
if !scopesGranted(consent.Scope, authorizeReq.Scope) {
c.JSON(200, SkipConsentResponse{
SkipConsent: false,
})
return
}
c.JSON(200, SkipConsentResponse{
SkipConsent: true,
})
}
// The actual **internal** endpoint that actually creates the code and session.
// It is called by the frontend after the user has logged in and given consent.
func (controller *OIDCController) authorizeComplete(c *gin.Context) {
@@ -330,6 +409,9 @@ func (controller *OIDCController) authorizeComplete(c *gin.Context) {
return
}
// We no longer need the ticket
controller.oidc.DeleteAuthorizeRequestTicket(req.Ticket)
// Get the client
client, ok := controller.oidc.GetClient(authorizeReq.ClientID)
@@ -343,9 +425,6 @@ func (controller *OIDCController) authorizeComplete(c *gin.Context) {
return
}
// We no longer need the ticket
controller.oidc.DeleteAuthorizeRequestTicket(req.Ticket)
// Create the sub to find and delete old sessions
sub := controller.oidc.CreateSub(*userContext, authorizeReq.ClientID)
+284 -72
View File
@@ -171,7 +171,7 @@ func TestOIDCController(t *testing.T) {
},
},
{
description: "Authorize skips the consent screen when all requested scopes were already granted",
description: "Authorize does not skip the consent screen even when consent was already granted",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
_, err := store.UpsertOIDCConsent(ctx, repository.UpsertOIDCConsentParams{
@@ -189,77 +189,9 @@ func TestOIDCController(t *testing.T) {
req := httptest.NewRequest("GET", "/authorize?"+q.Encode(), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusFound, recorder.Code)
location := recorder.Header().Get("Location")
assert.True(t, strings.HasPrefix(location, oidcService.GetIssuer()+"/oidc/authorize?"))
assert.Contains(t, location, "oidc_prompt=none")
},
},
{
description: "Authorize shows the consent screen when a new scope is requested",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
_, err := store.UpsertOIDCConsent(ctx, repository.UpsertOIDCConsentParams{
Username: "testuser", ClientID: "some-client-id",
Scope: "openid", CreatedAt: time.Now().Unix(),
})
require.NoError(t, err)
q := url.Values{}
q.Set("scope", "openid profile")
q.Set("response_type", "code")
q.Set("client_id", "some-client-id")
q.Set("redirect_uri", "https://test.example.com/callback")
req := httptest.NewRequest("GET", "/authorize?"+q.Encode(), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusFound, recorder.Code)
location := recorder.Header().Get("Location")
assert.True(t, strings.HasPrefix(location, oidcService.GetIssuer()+"/oidc/authorize?"))
assert.NotContains(t, location, "oidc_prompt=none")
},
},
{
description: "Authorize skips the consent screen for a subset of already granted scopes",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
_, err := store.UpsertOIDCConsent(ctx, repository.UpsertOIDCConsentParams{
Username: "testuser", ClientID: "some-client-id",
Scope: "openid profile email", CreatedAt: time.Now().Unix(),
})
require.NoError(t, err)
q := url.Values{}
q.Set("scope", "openid profile")
q.Set("response_type", "code")
q.Set("client_id", "some-client-id")
q.Set("redirect_uri", "https://test.example.com/callback")
req := httptest.NewRequest("GET", "/authorize?"+q.Encode(), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusFound, recorder.Code)
location := recorder.Header().Get("Location")
assert.True(t, strings.HasPrefix(location, oidcService.GetIssuer()+"/oidc/authorize?"))
assert.Contains(t, location, "oidc_prompt=none")
},
},
{
description: "Authorize shows the consent screen when no consent was granted yet",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
require.NoError(t, store.DeleteOIDCConsentByClientID(ctx, "some-client-id"))
q := url.Values{}
q.Set("scope", "openid profile")
q.Set("response_type", "code")
q.Set("client_id", "some-client-id")
q.Set("redirect_uri", "https://test.example.com/callback")
req := httptest.NewRequest("GET", "/authorize?"+q.Encode(), nil)
router.ServeHTTP(recorder, req)
// The consent skip check now happens after auth via the
// skip-consent endpoint, so authorize should never set
// prompt=none on its own.
assert.Equal(t, http.StatusFound, recorder.Code)
location := recorder.Header().Get("Location")
assert.True(t, strings.HasPrefix(location, oidcService.GetIssuer()+"/oidc/authorize?"))
@@ -304,6 +236,232 @@ func TestOIDCController(t *testing.T) {
},
},
// --- skip-consent ---
{
description: "Skip consent returns 500 when OIDC is not configured",
oidcDisabled: true,
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket=some-ticket", nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusInternalServerError, recorder.Code)
},
},
{
description: "Skip consent returns 401 when the user context is missing",
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket=some-ticket", nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusUnauthorized, recorder.Code)
},
},
{
description: "Skip consent returns 401 when the user is not authenticated",
middlewares: []gin.HandlerFunc{
func(c *gin.Context) {
c.Set("context", &model.UserContext{
Authenticated: false,
Provider: model.ProviderLocal,
Local: &model.LocalContext{
BaseContext: model.BaseContext{Username: "testuser"},
},
})
},
},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket=some-ticket", nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusUnauthorized, recorder.Code)
},
},
{
description: "Skip consent returns 400 when the ticket is missing",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/oidc/skip-consent", nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusBadRequest, recorder.Code)
},
},
{
description: "Skip consent returns false when the ticket is unknown",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket=nonexistent-ticket", nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
var res SkipConsentResponse
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
assert.False(t, res.SkipConsent)
},
},
{
description: "Skip consent returns true when the authorize request has prompt none",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
_, err := store.UpsertOIDCConsent(ctx, repository.UpsertOIDCConsentParams{
Username: "testuser", ClientID: "some-client-id",
Scope: "openid profile", CreatedAt: time.Now().Unix(),
})
require.NoError(t, err)
ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid profile",
ResponseType: "code",
ClientID: "some-client-id",
RedirectURI: "https://test.example.com/callback",
Prompt: "none",
})
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket="+url.QueryEscape(ticket), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
var res SkipConsentResponse
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
assert.True(t, res.SkipConsent)
},
},
{
description: "Skip consent returns false when no consent was granted yet",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
require.NoError(t, store.DeleteOIDCConsentByClientID(ctx, "some-client-id"))
ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid profile",
ResponseType: "code",
ClientID: "some-client-id",
RedirectURI: "https://test.example.com/callback",
})
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket="+url.QueryEscape(ticket), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
var res SkipConsentResponse
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
assert.False(t, res.SkipConsent)
},
},
{
description: "Skip consent returns false when a new scope is requested",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
_, err := store.UpsertOIDCConsent(ctx, repository.UpsertOIDCConsentParams{
Username: "testuser", ClientID: "some-client-id",
Scope: "openid", CreatedAt: time.Now().Unix(),
})
require.NoError(t, err)
ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid profile",
ResponseType: "code",
ClientID: "some-client-id",
RedirectURI: "https://test.example.com/callback",
})
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket="+url.QueryEscape(ticket), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
var res SkipConsentResponse
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
assert.False(t, res.SkipConsent)
},
},
{
description: "Skip consent returns true when all requested scopes were already granted",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
_, err := store.UpsertOIDCConsent(ctx, repository.UpsertOIDCConsentParams{
Username: "testuser", ClientID: "some-client-id",
Scope: "openid profile", CreatedAt: time.Now().Unix(),
})
require.NoError(t, err)
ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid profile",
ResponseType: "code",
ClientID: "some-client-id",
RedirectURI: "https://test.example.com/callback",
})
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket="+url.QueryEscape(ticket), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
var res SkipConsentResponse
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
assert.True(t, res.SkipConsent)
},
},
{
description: "Skip consent returns true for a subset of already granted scopes",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
_, err := store.UpsertOIDCConsent(ctx, repository.UpsertOIDCConsentParams{
Username: "testuser", ClientID: "some-client-id",
Scope: "openid profile email", CreatedAt: time.Now().Unix(),
})
require.NoError(t, err)
ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid profile",
ResponseType: "code",
ClientID: "some-client-id",
RedirectURI: "https://test.example.com/callback",
})
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket="+url.QueryEscape(ticket), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
var res SkipConsentResponse
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
assert.True(t, res.SkipConsent)
},
},
{
description: "Skip consent returns false when the consent belongs to another user",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
require.NoError(t, store.DeleteOIDCConsentByClientID(ctx, "some-client-id"))
_, err := store.UpsertOIDCConsent(ctx, repository.UpsertOIDCConsentParams{
Username: "otheruser", ClientID: "some-client-id",
Scope: "openid profile", CreatedAt: time.Now().Unix(),
})
require.NoError(t, err)
ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid profile",
ResponseType: "code",
ClientID: "some-client-id",
RedirectURI: "https://test.example.com/callback",
})
req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket="+url.QueryEscape(ticket), nil)
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
var res SkipConsentResponse
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
assert.False(t, res.SkipConsent)
},
},
// --- authorize-complete ---
{
description: "Should fail if oidc is disabled",
@@ -423,6 +581,60 @@ func TestOIDCController(t *testing.T) {
assert.Contains(t, redirectURI, "state=state-123")
},
},
{
description: "Authorize complete deletes the ticket on success",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid",
ResponseType: "code",
ClientID: "some-client-id",
RedirectURI: "https://test.example.com/callback",
})
body, err := json.Marshal(AuthorizeCompleteRequest{Ticket: ticket})
require.NoError(t, err)
req := httptest.NewRequest("POST", "/api/oidc/authorize-complete", strings.NewReader(string(body)))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
_, ok := oidcService.GetAuthorizeRequestByTicket(ticket)
assert.False(t, ok)
},
},
{
description: "Authorize complete deletes the ticket even when the client is unknown",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid",
ResponseType: "code",
ClientID: "unknown-client",
RedirectURI: "https://test.example.com/callback",
})
body, err := json.Marshal(AuthorizeCompleteRequest{Ticket: ticket})
require.NoError(t, err)
req := httptest.NewRequest("POST", "/api/oidc/authorize-complete", strings.NewReader(string(body)))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(recorder, req)
assert.Equal(t, http.StatusOK, recorder.Code)
var res map[string]any
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
redirectURI, ok := res["redirect_uri"].(string)
require.True(t, ok)
assert.Contains(t, redirectURI, oidcService.GetIssuer()+"/error")
_, ok = oidcService.GetAuthorizeRequestByTicket(ticket)
assert.False(t, ok)
},
},
// --- token ---
{
+11
View File
@@ -53,6 +53,17 @@ const (
OIDCPromptNone OIDCPrompt = "none"
)
func (p OIDCPrompt) String() string {
switch p {
case OIDCPromptLogin:
return "login"
case OIDCPromptNone:
return "none"
default:
return "login"
}
}
var SupportedPrompts = []string{string(OIDCPromptLogin), string(OIDCPromptNone)}
// This is not spec-compliant, the ID token SHOULD NOT contain user info claims but,