mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(core): redirect unknown session to app fallback uri
redirect unknown session to app fallback uri
This commit is contained in:
parent
49ca7d01a8
commit
b483800e6a
3 changed files with 67 additions and 4 deletions
|
@ -6,6 +6,8 @@ import { EnvSet, UserApps } from '#src/env-set/index.js';
|
|||
import { MockQueries } from '#src/test-utils/tenant.js';
|
||||
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
|
||||
|
||||
import { LoginQueryParamsKey } from '../oidc/utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
@ -25,7 +27,11 @@ describe('koaSpaSessionGuard', () => {
|
|||
const provider = new Provider('https://logto.test');
|
||||
const interactionDetails = jest.spyOn(provider, 'interactionDetails');
|
||||
const getRowsByKeys = jest.fn().mockResolvedValue({ rows: [] });
|
||||
const queries = new MockQueries({ logtoConfigs: { getRowsByKeys } });
|
||||
const findApplicationById = jest.fn();
|
||||
const queries = new MockQueries({
|
||||
logtoConfigs: { getRowsByKeys },
|
||||
applications: { findApplicationById },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = { ...envBackup };
|
||||
|
@ -115,4 +121,21 @@ describe('koaSpaSessionGuard', () => {
|
|||
expect(ctx.redirect).toBeCalledWith('https://test.com/unknown-session');
|
||||
stub.restore();
|
||||
});
|
||||
|
||||
it('should redirect to configured application fallback URL if session not found', async () => {
|
||||
const unknownSessionFallbackUri = 'https://foo.bar';
|
||||
findApplicationById.mockResolvedValueOnce({
|
||||
unknownSessionFallbackUri,
|
||||
});
|
||||
|
||||
const appId = '123';
|
||||
interactionDetails.mockRejectedValue(new Error('session not found'));
|
||||
const ctx = createContextWithRouteParameters({
|
||||
url: `${guardedPath[0]!}?${LoginQueryParamsKey.AppId}=${appId}`,
|
||||
});
|
||||
|
||||
await koaSpaSessionGuard(provider, queries)(ctx, next);
|
||||
expect(findApplicationById).toBeCalledWith(appId);
|
||||
expect(ctx.redirect).toBeCalledWith(unknownSessionFallbackUri);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { logtoConfigGuards, LogtoTenantConfigKey } from '@logto/schemas';
|
||||
import {
|
||||
logtoConfigGuards,
|
||||
logtoCookieKey,
|
||||
LogtoTenantConfigKey,
|
||||
logtoUiCookieGuard,
|
||||
} from '@logto/schemas';
|
||||
import { appendPath, trySafe } from '@silverhand/essentials';
|
||||
import type { MiddlewareType } from 'koa';
|
||||
import type { MiddlewareType, ParameterizedContext } from 'koa';
|
||||
import type { IRouterParamContext } from 'koa-router';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
|
@ -9,6 +14,8 @@ import RequestError from '#src/errors/RequestError/index.js';
|
|||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { getTenantId } from '#src/utils/tenant.js';
|
||||
|
||||
import { LoginQueryParamsKey } from '../oidc/utils.js';
|
||||
|
||||
// Need To Align With UI
|
||||
export const sessionNotFoundPath = '/unknown-session';
|
||||
|
||||
|
@ -21,6 +28,24 @@ export const guardedPath = [
|
|||
'/forgot-password',
|
||||
];
|
||||
|
||||
/**
|
||||
* Retrieve the appId from ctx for find the fallback url
|
||||
* First check the query param appId
|
||||
* If not found, check the logto ui cookie
|
||||
*/
|
||||
const getAppIdFromContext = (ctx: ParameterizedContext) => {
|
||||
const appId = ctx.request.URL.searchParams.get(LoginQueryParamsKey.AppId);
|
||||
|
||||
if (appId) {
|
||||
return appId;
|
||||
}
|
||||
|
||||
const cookie = ctx.cookies.get(logtoCookieKey);
|
||||
const parsed = trySafe(() => logtoUiCookieGuard.parse(cookie));
|
||||
|
||||
return parsed?.appId;
|
||||
};
|
||||
|
||||
export default function koaSpaSessionGuard<
|
||||
StateT,
|
||||
ContextT extends IRouterParamContext,
|
||||
|
@ -37,6 +62,17 @@ export default function koaSpaSessionGuard<
|
|||
try {
|
||||
await provider.interactionDetails(ctx.req, ctx.res);
|
||||
} catch {
|
||||
// Try to find the fallback url for the application
|
||||
const appId = getAppIdFromContext(ctx);
|
||||
const application = appId
|
||||
? await trySafe(async () => queries.applications.findApplicationById(appId))
|
||||
: undefined;
|
||||
|
||||
if (application?.unknownSessionFallbackUri) {
|
||||
ctx.redirect(application.unknownSessionFallbackUri);
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
rows: [data],
|
||||
} = await queries.logtoConfigs.getRowsByKeys([
|
||||
|
|
|
@ -96,6 +96,10 @@ const firstScreenRouteMapping: Record<FirstScreen, keyof typeof experience.route
|
|||
[FirstScreen.SignInDeprecated]: 'signIn',
|
||||
};
|
||||
|
||||
export enum LoginQueryParamsKey {
|
||||
AppId = 'app_id',
|
||||
}
|
||||
|
||||
// Note: this eslint comment can be removed once the dev feature flag is removed
|
||||
|
||||
export const buildLoginPromptUrl = (params: ExtraParamsObject, appId?: unknown): string => {
|
||||
|
@ -114,7 +118,7 @@ export const buildLoginPromptUrl = (params: ExtraParamsObject, appId?: unknown):
|
|||
const getSearchParamString = () => (searchParams.size > 0 ? `?${searchParams.toString()}` : '');
|
||||
|
||||
if (appId) {
|
||||
searchParams.append('app_id', String(appId));
|
||||
searchParams.append(LoginQueryParamsKey.AppId, String(appId));
|
||||
}
|
||||
|
||||
if (params[ExtraParamsKey.OrganizationId]) {
|
||||
|
|
Loading…
Reference in a new issue