mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-06-20 18:30:14 +00:00
feat: allow for prompt to skip authorize screen
This commit is contained in:
@@ -6,7 +6,7 @@ type ScreenParams = {
|
||||
oidc_ticket?: string;
|
||||
oidc_scope?: string;
|
||||
oidc_name?: string;
|
||||
oidc_login?: boolean;
|
||||
oidc_prompt?: "none" | "login";
|
||||
};
|
||||
|
||||
const zodScreenParams = z.object({
|
||||
@@ -15,7 +15,7 @@ const zodScreenParams = z.object({
|
||||
oidc_ticket: z.string().optional(),
|
||||
oidc_scope: z.string().optional(),
|
||||
oidc_name: z.string().optional(),
|
||||
oidc_login: z.stringbool().optional(),
|
||||
oidc_prompt: z.enum(["none", "login"]).optional(),
|
||||
});
|
||||
|
||||
export function useScreenParams(params: URLSearchParams): ScreenParams {
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
recompileScreenParams,
|
||||
useScreenParams,
|
||||
} from "@/lib/hooks/screen-params";
|
||||
import { useEffect } from "react";
|
||||
|
||||
type Scope = {
|
||||
id: string;
|
||||
@@ -90,7 +91,15 @@ export const AuthorizePage = () => {
|
||||
const isOidc = screenParams.login_for === "oidc";
|
||||
const compiledParams = recompileScreenParams(screenParams);
|
||||
|
||||
const authorizeMutation = useMutation({
|
||||
// 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 { mutate: authorizeMutate, isPending: authorizePending } = useMutation({
|
||||
mutationFn: () => {
|
||||
return axios.post("/api/oidc/authorize-complete", {
|
||||
ticket: screenParams.oidc_ticket,
|
||||
@@ -110,6 +119,12 @@ export const AuthorizePage = () => {
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldAutoAuthorize) {
|
||||
authorizeMutate();
|
||||
}
|
||||
}, [shouldAutoAuthorize, authorizeMutate]);
|
||||
|
||||
if (!isOidc || !screenParams.oidc_ticket || !screenParams.oidc_scope) {
|
||||
return (
|
||||
<Navigate
|
||||
@@ -119,7 +134,7 @@ export const AuthorizePage = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (!auth.authenticated || screenParams.oidc_login) {
|
||||
if (!auth.authenticated || screenParams.oidc_prompt === "login") {
|
||||
return <Navigate to={`/login${compiledParams}`} replace />;
|
||||
}
|
||||
|
||||
@@ -168,14 +183,15 @@ export const AuthorizePage = () => {
|
||||
)}
|
||||
<CardFooter className="flex flex-col items-stretch gap-3">
|
||||
<Button
|
||||
onClick={() => authorizeMutation.mutate()}
|
||||
loading={authorizeMutation.isPending}
|
||||
onClick={() => authorizeMutate()}
|
||||
loading={authorizePending}
|
||||
disabled={shouldAutoAuthorize}
|
||||
>
|
||||
{t("authorizeTitle")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate(`/logout${compiledParams}`)}
|
||||
disabled={authorizeMutation.isPending}
|
||||
disabled={authorizePending || shouldAutoAuthorize}
|
||||
variant="outline"
|
||||
>
|
||||
{t("cancelTitle")}
|
||||
|
||||
@@ -65,7 +65,7 @@ export const LoginPage = () => {
|
||||
const screenParams = useScreenParams(searchParams);
|
||||
const compiledParams = recompileScreenParams({
|
||||
...screenParams,
|
||||
oidc_login: false,
|
||||
oidc_prompt: undefined,
|
||||
});
|
||||
const loginForUrl = useLoginFor({
|
||||
login_for: screenParams.login_for,
|
||||
@@ -199,7 +199,7 @@ export const LoginPage = () => {
|
||||
};
|
||||
}, [redirectTimer, redirectButtonTimer]);
|
||||
|
||||
if (auth.authenticated && !screenParams.oidc_login) {
|
||||
if (auth.authenticated && screenParams.oidc_prompt !== "login") {
|
||||
return <Navigate to={loginForUrl} replace />;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,11 +69,11 @@ type ClientCredentials struct {
|
||||
}
|
||||
|
||||
type AuthorizeScreenParams struct {
|
||||
LoginFor FrontendLoginFor `url:"login_for"`
|
||||
OIDCTicket string `url:"oidc_ticket"`
|
||||
OIDCScope string `url:"oidc_scope"`
|
||||
OIDCName string `url:"oidc_name"`
|
||||
OIDCLogin bool `url:"oidc_login"`
|
||||
LoginFor FrontendLoginFor `url:"login_for"`
|
||||
OIDCTicket string `url:"oidc_ticket"`
|
||||
OIDCScope string `url:"oidc_scope"`
|
||||
OIDCName string `url:"oidc_name"`
|
||||
OIDCPrompt service.OIDCPrompt `url:"oidc_prompt,omitempty"`
|
||||
}
|
||||
|
||||
type AuthorizeCompleteRequest struct {
|
||||
@@ -168,6 +168,8 @@ func (controller *OIDCController) authorize(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
prompt := controller.oidc.GetPrompt(req.Prompt)
|
||||
|
||||
userContext, err := new(model.UserContext).NewFromGin(c)
|
||||
|
||||
if err != nil {
|
||||
@@ -176,7 +178,7 @@ func (controller *OIDCController) authorize(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
if (err != nil || !userContext.Authenticated) && req.Prompt == "none" {
|
||||
if (err != nil || !userContext.Authenticated) && prompt == service.OIDCPromptNone {
|
||||
controller.authorizeError(c, authorizeErrorParams{
|
||||
err: errors.New("user not logged in"),
|
||||
reason: "User not logged in",
|
||||
@@ -195,10 +197,7 @@ func (controller *OIDCController) authorize(c *gin.Context) {
|
||||
OIDCTicket: ticket,
|
||||
OIDCScope: req.Scope,
|
||||
OIDCName: client.Name,
|
||||
}
|
||||
|
||||
if req.Prompt == "login" {
|
||||
values.OIDCLogin = true
|
||||
OIDCPrompt: prompt,
|
||||
}
|
||||
|
||||
queries, err := query.Values(values)
|
||||
|
||||
@@ -44,6 +44,15 @@ var (
|
||||
ErrInvalidClient = errors.New("invalid_client")
|
||||
)
|
||||
|
||||
type OIDCPrompt string
|
||||
|
||||
const (
|
||||
OIDCPromptLogin OIDCPrompt = "login"
|
||||
OIDCPromptNone OIDCPrompt = "none"
|
||||
)
|
||||
|
||||
var SupportedPrompts = []string{string(OIDCPromptLogin), string(OIDCPromptNone)}
|
||||
|
||||
// This is not spec-compliant, the ID token SHOULD NOT contain user info claims but,
|
||||
// it has became a "standard" and apps are looking for the claims in the ID tokens
|
||||
// instead of calling the userinfo endpoint, so we include them in the ID token as well
|
||||
@@ -937,3 +946,20 @@ func (service *OIDCService) DecodeAuthorizeJWT(tokenString string) (*AuthorizeRe
|
||||
Prompt: get("prompt"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Return the first prompt in the list of prompts, or an empty string if no prompt is specified
|
||||
func (service *OIDCService) GetPrompt(prompt string) OIDCPrompt {
|
||||
if prompt == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
prompts := strings.Split(prompt, " ")
|
||||
|
||||
for _, p := range prompts {
|
||||
if slices.Contains(SupportedPrompts, p) {
|
||||
return OIDCPrompt(p)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user