mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Auth: Show OIDC sign-in errors persistently and arm the loop guard
An OIDC sign-in failure (e.g. a cluster 'no access to this instance' denial) was shown only as a transient toast the user could miss and retry into a denial loop. Render it as a persistent, dismissible alert on the login form instead, driven by the existing session.error bridge. Also mark the redirect-loop guard when the user clicks the OIDC button, so a bounce-back is recognized like the /login route guard's auto-redirect.
This commit is contained in:
parent
0379692a46
commit
4b7565b3ac
2 changed files with 54 additions and 1 deletions
|
|
@ -25,6 +25,16 @@
|
|||
<v-card-text>
|
||||
<p-auth-header></p-auth-header>
|
||||
<v-spacer></v-spacer>
|
||||
<v-alert
|
||||
v-if="loginError"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
closable
|
||||
class="auth-error mb-3"
|
||||
@click:close="loginError = ''"
|
||||
>{{ loginError }}</v-alert
|
||||
>
|
||||
<v-row align="start" dense>
|
||||
<template v-if="enterCode">
|
||||
<v-col cols="12" class="text-body-2 text-center pb-1">
|
||||
|
|
@ -218,6 +228,7 @@ export default {
|
|||
useRecoveryCode: false,
|
||||
code: "",
|
||||
enterCode: false,
|
||||
loginError: "",
|
||||
sponsor: this.$config.isSponsor(),
|
||||
config: this.$config.values,
|
||||
siteDescription: this.$config.getSiteDescription(),
|
||||
|
|
@ -267,9 +278,12 @@ export default {
|
|||
created() {
|
||||
this.staySignedIn = this.currentStaySignedInState();
|
||||
|
||||
// Surface an OIDC sign-in failure (e.g. "no access to this instance")
|
||||
// as a persistent, dismissible alert on the form rather than a transient
|
||||
// toast the user can miss and retry into a denial loop.
|
||||
const authError = getAppStorage().getItem("session.error");
|
||||
if (authError) {
|
||||
this.$notify.error(authError);
|
||||
this.loginError = authError;
|
||||
getAppStorage().removeItem("session.error");
|
||||
}
|
||||
},
|
||||
|
|
@ -377,6 +391,10 @@ export default {
|
|||
if (this.config.ext?.oidc?.loginUri) {
|
||||
this.applySessionPersistence();
|
||||
this.loading = true;
|
||||
// Mark the redirect follow so a bounce-back is recognized as a loop by
|
||||
// the /login route guard (common/session.js loginRedirectLooping) — the
|
||||
// manual OIDC button must participate in the guard like the auto-redirect.
|
||||
this.$session.markLoginRedirectAttempt();
|
||||
this.$session.followRedirect(this.config.ext.oidc.loginUri);
|
||||
} else {
|
||||
this.$notify.warn(this.$gettext("Missing or invalid configuration"));
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ function mountLogin({ oidcEnabled = false, oidcRedirect = false, sessionOverride
|
|||
getDefaultRoute: vi.fn(() => "browse"),
|
||||
isAuthenticated: vi.fn(() => false),
|
||||
consumeLogoutSignal: vi.fn(() => false),
|
||||
markLoginRedirectAttempt: vi.fn(),
|
||||
...baseSession,
|
||||
...sessionOverrides,
|
||||
};
|
||||
|
|
@ -137,6 +138,40 @@ describe("page/auth/login", () => {
|
|||
expect(session.useSessionStorage).toHaveBeenCalledTimes(1);
|
||||
expect(session.useLocalStorage).not.toHaveBeenCalled();
|
||||
expect(session.followRedirect).toHaveBeenCalledWith("/api/v1/oidc/login");
|
||||
// The manual OIDC button must arm the redirect-loop guard, like the
|
||||
// auto-redirect in the /login route guard does.
|
||||
expect(session.markLoginRedirectAttempt).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("reset clears the inputs and code-entry state", () => {
|
||||
const { wrapper } = mountLogin();
|
||||
|
||||
Object.assign(wrapper.vm, {
|
||||
username: "x",
|
||||
password: "y",
|
||||
showPassword: true,
|
||||
useRecoveryCode: true,
|
||||
code: "123456",
|
||||
enterCode: true,
|
||||
});
|
||||
wrapper.vm.reset();
|
||||
|
||||
expect(wrapper.vm.username).toBe("");
|
||||
expect(wrapper.vm.password).toBe("");
|
||||
expect(wrapper.vm.showPassword).toBe(false);
|
||||
expect(wrapper.vm.useRecoveryCode).toBe(false);
|
||||
expect(wrapper.vm.code).toBe("");
|
||||
expect(wrapper.vm.enterCode).toBe(false);
|
||||
});
|
||||
|
||||
it("surfaces a stored OIDC sign-in error as a persistent login error and consumes it", () => {
|
||||
window.localStorage.setItem(`${storagePrefix}session.error`, "You do not have access to this instance.");
|
||||
|
||||
const { wrapper } = mountLogin({ oidcEnabled: true });
|
||||
|
||||
expect(wrapper.vm.loginError).toBe("You do not have access to this instance.");
|
||||
// Consumed so it does not reappear on the next reload.
|
||||
expect(window.localStorage.getItem(`${storagePrefix}session.error`)).toBeNull();
|
||||
});
|
||||
|
||||
// Auto-OIDC redirect for unauthenticated visitors now lives in the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue