mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
refactor(core): session routes
This commit is contained in:
parent
67305ec407
commit
3d6763934f
9 changed files with 295 additions and 217 deletions
|
@ -14,9 +14,7 @@ import dashboardRoutes from '@/routes/dashboard';
|
|||
import logRoutes from '@/routes/log';
|
||||
import resourceRoutes from '@/routes/resource';
|
||||
import roleRoutes from '@/routes/role';
|
||||
import sessionPasswordlessRoutes from '@/routes/session/passwordless';
|
||||
import sessionRoutes from '@/routes/session/session';
|
||||
import sessionSocialRoutes from '@/routes/session/social';
|
||||
import sessionRoutes from '@/routes/session';
|
||||
import settingRoutes from '@/routes/setting';
|
||||
import signInExperiencesRoutes from '@/routes/sign-in-experience';
|
||||
import statusRoutes from '@/routes/status';
|
||||
|
@ -29,8 +27,6 @@ const createRouters = (provider: Provider) => {
|
|||
const sessionRouter: AnonymousRouter = new Router();
|
||||
sessionRouter.use(koaLogSession(provider));
|
||||
sessionRoutes(sessionRouter, provider);
|
||||
sessionPasswordlessRoutes(sessionRouter, provider);
|
||||
sessionSocialRoutes(sessionRouter, provider);
|
||||
|
||||
const managementRouter: AuthedRouter = new Router();
|
||||
managementRouter.use(koaAuth(UserRole.Admin));
|
||||
|
|
195
packages/core/src/routes/session/index.test.ts
Normal file
195
packages/core/src/routes/session/index.test.ts
Normal file
|
@ -0,0 +1,195 @@
|
|||
import { User } from '@logto/schemas';
|
||||
import { Provider } from 'oidc-provider';
|
||||
|
||||
import { mockUser } from '@/__mocks__';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import sessionRoutes from '.';
|
||||
|
||||
const findUserById = jest.fn(async (): Promise<User> => mockUser);
|
||||
const updateUserById = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
|
||||
const grantSave = jest.fn(async () => 'finalGrantId');
|
||||
const grantAddOIDCScope = jest.fn();
|
||||
const grantAddResourceScope = jest.fn();
|
||||
const interactionResult = jest.fn(async () => 'redirectTo');
|
||||
const interactionDetails: jest.MockedFunction<() => Promise<unknown>> = jest.fn(async () => ({}));
|
||||
|
||||
jest.mock('@/queries/user', () => ({
|
||||
findUserById: async () => findUserById(),
|
||||
updateUserById: async (...args: unknown[]) => updateUserById(...args),
|
||||
}));
|
||||
|
||||
class Grant {
|
||||
static async find(id: string) {
|
||||
return id === 'exists' ? new Grant() : undefined;
|
||||
}
|
||||
|
||||
save: typeof grantSave;
|
||||
addOIDCScope: typeof grantAddOIDCScope;
|
||||
addResourceScope: typeof grantAddResourceScope;
|
||||
|
||||
constructor() {
|
||||
this.save = grantSave;
|
||||
this.addOIDCScope = grantAddOIDCScope;
|
||||
this.addResourceScope = grantAddResourceScope;
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('oidc-provider', () => ({
|
||||
Provider: jest.fn(() => ({
|
||||
Grant,
|
||||
interactionDetails,
|
||||
interactionResult,
|
||||
})),
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
grantSave.mockClear();
|
||||
interactionResult.mockClear();
|
||||
});
|
||||
|
||||
describe('sessionRoutes', () => {
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: sessionRoutes,
|
||||
provider: new Provider(''),
|
||||
middlewares: [
|
||||
async (ctx, next) => {
|
||||
ctx.addLogContext = jest.fn();
|
||||
ctx.log = jest.fn();
|
||||
|
||||
return next();
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
describe('POST /session', () => {
|
||||
it('should redirect to /session/consent with consent prompt name', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
prompt: { name: 'consent' },
|
||||
});
|
||||
const response = await sessionRequest.post('/session');
|
||||
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.body).toHaveProperty(
|
||||
'redirectTo',
|
||||
expect.stringContaining('/session/consent')
|
||||
);
|
||||
});
|
||||
|
||||
it('throw error with other prompt name', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
prompt: { name: 'invalid' },
|
||||
});
|
||||
await expect(sessionRequest.post('/session').send({})).resolves.toHaveProperty('status', 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /session/consent', () => {
|
||||
describe('should call grant.save() and assign interaction results', () => {
|
||||
afterEach(() => {
|
||||
updateUserById.mockClear();
|
||||
});
|
||||
|
||||
it('with empty details and reusing old grant', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
session: { accountId: 'accountId' },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: { details: {} },
|
||||
});
|
||||
const response = await sessionRequest.post('/session/consent');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(grantSave).toHaveBeenCalled();
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
it('with empty details and creating new grant', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
session: { accountId: 'accountId' },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: { details: {} },
|
||||
grantId: 'exists',
|
||||
});
|
||||
const response = await sessionRequest.post('/session/consent');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(grantSave).toHaveBeenCalled();
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
it('should save application id when the user first consented', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
session: { accountId: mockUser.id },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: {
|
||||
name: 'consent',
|
||||
details: {},
|
||||
reasons: ['consent_prompt', 'native_client_prompt'],
|
||||
},
|
||||
grantId: 'grantId',
|
||||
});
|
||||
findUserById.mockImplementationOnce(async () => ({ ...mockUser, applicationId: null }));
|
||||
const response = await sessionRequest.post('/session/consent');
|
||||
expect(updateUserById).toHaveBeenCalledWith(mockUser.id, { applicationId: 'clientId' });
|
||||
expect(response.statusCode).toEqual(200);
|
||||
});
|
||||
it('missingOIDCScope and missingResourceScopes', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
session: { accountId: 'accountId' },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: {
|
||||
details: {
|
||||
missingOIDCScope: ['scope1', 'scope2'],
|
||||
missingResourceScopes: {
|
||||
resource1: ['scope1', 'scope2'],
|
||||
resource2: ['scope3'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const response = await sessionRequest.post('/session/consent');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(grantAddOIDCScope).toHaveBeenCalledWith('scope1 scope2');
|
||||
expect(grantAddResourceScope).toHaveBeenCalledWith('resource1', 'scope1 scope2');
|
||||
expect(grantAddResourceScope).toHaveBeenCalledWith('resource2', 'scope3');
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
it('throws if session is missing', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: { client_id: 'clientId' } });
|
||||
await expect(sessionRequest.post('/session/consent')).resolves.toHaveProperty(
|
||||
'statusCode',
|
||||
400
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE /session', async () => {
|
||||
const response = await sessionRequest.delete('/session');
|
||||
expect(response.body).toHaveProperty('redirectTo');
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ error: 'oidc.aborted' }),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
85
packages/core/src/routes/session/index.ts
Normal file
85
packages/core/src/routes/session/index.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import path from 'path';
|
||||
|
||||
import { LogtoErrorCode } from '@logto/phrases';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { Provider } from 'oidc-provider';
|
||||
import { object, string } from 'zod';
|
||||
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { assignInteractionResults, saveUserFirstConsentedAppId } from '@/lib/session';
|
||||
import assertThat from '@/utils/assert-that';
|
||||
|
||||
import { AnonymousRouter } from '../types';
|
||||
import passwordlessRoutes from './passwordless';
|
||||
import socialRoutes from './social';
|
||||
import usernamePasswordRoutes from './username-password';
|
||||
|
||||
export default function sessionRoutes<T extends AnonymousRouter>(router: T, provider: Provider) {
|
||||
router.post('/session', async (ctx, next) => {
|
||||
const {
|
||||
prompt: { name },
|
||||
} = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
if (name === 'consent') {
|
||||
ctx.body = { redirectTo: path.join(ctx.request.origin, '/session/consent') };
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
throw new RequestError('session.unsupported_prompt_name');
|
||||
});
|
||||
|
||||
router.post('/session/consent', async (ctx, next) => {
|
||||
const interaction = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const {
|
||||
session,
|
||||
grantId,
|
||||
params: { client_id },
|
||||
prompt,
|
||||
} = interaction;
|
||||
assertThat(session, 'session.not_found');
|
||||
|
||||
const { accountId } = session;
|
||||
const grant =
|
||||
conditional(grantId && (await provider.Grant.find(grantId))) ??
|
||||
new provider.Grant({ accountId, clientId: String(client_id) });
|
||||
|
||||
await saveUserFirstConsentedAppId(accountId, String(client_id));
|
||||
|
||||
// V2: fulfill missing claims / resources
|
||||
const PromptDetailsBody = object({
|
||||
missingOIDCScope: string().array().optional(),
|
||||
missingResourceScopes: object({}).catchall(string().array()).optional(),
|
||||
});
|
||||
const { missingOIDCScope, missingResourceScopes } = PromptDetailsBody.parse(prompt.details);
|
||||
|
||||
if (missingOIDCScope) {
|
||||
grant.addOIDCScope(missingOIDCScope.join(' '));
|
||||
}
|
||||
|
||||
if (missingResourceScopes) {
|
||||
for (const [indicator, scope] of Object.entries(missingResourceScopes)) {
|
||||
grant.addResourceScope(indicator, scope.join(' '));
|
||||
}
|
||||
}
|
||||
|
||||
const finalGrantId = await grant.save();
|
||||
|
||||
// V2: configure consent
|
||||
await assignInteractionResults(ctx, provider, { consent: { grantId: finalGrantId } }, true);
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.delete('/session', async (ctx, next) => {
|
||||
await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const error: LogtoErrorCode = 'oidc.aborted';
|
||||
await assignInteractionResults(ctx, provider, { error });
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
usernamePasswordRoutes(router, provider);
|
||||
passwordlessRoutes(router, provider);
|
||||
socialRoutes(router, provider);
|
||||
}
|
|
@ -5,7 +5,7 @@ import { mockUser } from '@/__mocks__';
|
|||
import RequestError from '@/errors/RequestError';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import sessionPasswordlessRoutes from './passwordless';
|
||||
import passwordlessRoutes from './passwordless';
|
||||
|
||||
const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const findUserById = jest.fn(async (): Promise<User> => mockUser);
|
||||
|
@ -52,9 +52,9 @@ afterEach(() => {
|
|||
interactionResult.mockClear();
|
||||
});
|
||||
|
||||
describe('sessionPasswordlessRoutes', () => {
|
||||
describe('session -> passwordlessRoutes', () => {
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: sessionPasswordlessRoutes,
|
||||
anonymousRoutes: passwordlessRoutes,
|
||||
provider: new Provider(''),
|
||||
middlewares: [
|
||||
async (ctx, next) => {
|
||||
|
|
|
@ -18,7 +18,7 @@ import assertThat from '@/utils/assert-that';
|
|||
|
||||
import { AnonymousRouter } from '../types';
|
||||
|
||||
export default function sessionPasswordlessRoutes<T extends AnonymousRouter>(
|
||||
export default function passwordlessRoutes<T extends AnonymousRouter>(
|
||||
router: T,
|
||||
provider: Provider
|
||||
) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { ConnectorType } from '@/connectors/types';
|
|||
import RequestError from '@/errors/RequestError';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import sessionSocialRoutes from './social';
|
||||
import socialRoutes from './social';
|
||||
|
||||
jest.mock('@/lib/social', () => ({
|
||||
...jest.requireActual('@/lib/social'),
|
||||
|
@ -97,9 +97,9 @@ afterEach(() => {
|
|||
interactionResult.mockClear();
|
||||
});
|
||||
|
||||
describe('sessionSocialRoutes', () => {
|
||||
describe('session -> socialRoutes', () => {
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: sessionSocialRoutes,
|
||||
anonymousRoutes: socialRoutes,
|
||||
provider: new Provider(''),
|
||||
middlewares: [
|
||||
async (ctx, next) => {
|
||||
|
|
|
@ -25,10 +25,7 @@ import { maskUserInfo } from '@/utils/format';
|
|||
|
||||
import { AnonymousRouter } from '../types';
|
||||
|
||||
export default function sessionSocialRoutes<T extends AnonymousRouter>(
|
||||
router: T,
|
||||
provider: Provider
|
||||
) {
|
||||
export default function socialRoutes<T extends AnonymousRouter>(router: T, provider: Provider) {
|
||||
router.post(
|
||||
'/session/sign-in/social',
|
||||
koaGuard({
|
||||
|
|
|
@ -6,7 +6,7 @@ import { mockUser } from '@/__mocks__';
|
|||
import RequestError from '@/errors/RequestError';
|
||||
import { createRequester } from '@/utils/test-utils';
|
||||
|
||||
import sessionRoutes from './session';
|
||||
import sessionRoutes from '.';
|
||||
|
||||
const insertUser = jest.fn(async (..._args: unknown[]) => ({ id: 'id' }));
|
||||
const findUserById = jest.fn(async (): Promise<User> => mockUser);
|
||||
|
@ -99,28 +99,6 @@ describe('sessionRoutes', () => {
|
|||
],
|
||||
});
|
||||
|
||||
describe('POST /session', () => {
|
||||
it('should redirect to /session/consent with consent prompt name', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
prompt: { name: 'consent' },
|
||||
});
|
||||
const response = await sessionRequest.post('/session');
|
||||
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.body).toHaveProperty(
|
||||
'redirectTo',
|
||||
expect.stringContaining('/session/consent')
|
||||
);
|
||||
});
|
||||
|
||||
it('throw error with other prompt name', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
prompt: { name: 'invalid' },
|
||||
});
|
||||
await expect(sessionRequest.post('/session').send({})).resolves.toHaveProperty('status', 400);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /session/sign-in/username-password', () => {
|
||||
it('assign result and redirect', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: {} });
|
||||
|
@ -256,112 +234,4 @@ describe('sessionRoutes', () => {
|
|||
expect(response.statusCode).toEqual(422);
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /session/consent', () => {
|
||||
describe('should call grant.save() and assign interaction results', () => {
|
||||
afterEach(() => {
|
||||
updateUserById.mockClear();
|
||||
});
|
||||
|
||||
it('with empty details and reusing old grant', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
session: { accountId: 'accountId' },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: { details: {} },
|
||||
});
|
||||
const response = await sessionRequest.post('/session/consent');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(grantSave).toHaveBeenCalled();
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
it('with empty details and creating new grant', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
session: { accountId: 'accountId' },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: { details: {} },
|
||||
grantId: 'exists',
|
||||
});
|
||||
const response = await sessionRequest.post('/session/consent');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(grantSave).toHaveBeenCalled();
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
it('should save application id when the user first consented', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
session: { accountId: mockUser.id },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: {
|
||||
name: 'consent',
|
||||
details: {},
|
||||
reasons: ['consent_prompt', 'native_client_prompt'],
|
||||
},
|
||||
grantId: 'grantId',
|
||||
});
|
||||
findUserById.mockImplementationOnce(async () => ({ ...mockUser, applicationId: null }));
|
||||
const response = await sessionRequest.post('/session/consent');
|
||||
expect(updateUserById).toHaveBeenCalledWith(mockUser.id, { applicationId: 'clientId' });
|
||||
expect(response.statusCode).toEqual(200);
|
||||
});
|
||||
it('missingOIDCScope and missingResourceScopes', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({
|
||||
session: { accountId: 'accountId' },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: {
|
||||
details: {
|
||||
missingOIDCScope: ['scope1', 'scope2'],
|
||||
missingResourceScopes: {
|
||||
resource1: ['scope1', 'scope2'],
|
||||
resource2: ['scope3'],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const response = await sessionRequest.post('/session/consent');
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(grantAddOIDCScope).toHaveBeenCalledWith('scope1 scope2');
|
||||
expect(grantAddResourceScope).toHaveBeenCalledWith('resource1', 'scope1 scope2');
|
||||
expect(grantAddResourceScope).toHaveBeenCalledWith('resource2', 'scope3');
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
it('throws if session is missing', async () => {
|
||||
interactionDetails.mockResolvedValueOnce({ params: { client_id: 'clientId' } });
|
||||
await expect(sessionRequest.post('/session/consent')).resolves.toHaveProperty(
|
||||
'statusCode',
|
||||
400
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('DELETE /session', async () => {
|
||||
const response = await sessionRequest.delete('/session');
|
||||
expect(response.body).toHaveProperty('redirectTo');
|
||||
expect(interactionResult).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({ error: 'oidc.aborted' }),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,15 +1,11 @@
|
|||
import path from 'path';
|
||||
|
||||
import { LogtoErrorCode } from '@logto/phrases';
|
||||
import { UserRole } from '@logto/schemas';
|
||||
import { adminConsoleApplicationId } from '@logto/schemas/lib/seeds';
|
||||
import { passwordRegEx, usernameRegEx } from '@logto/shared';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { Provider } from 'oidc-provider';
|
||||
import { object, string } from 'zod';
|
||||
|
||||
import RequestError from '@/errors/RequestError';
|
||||
import { assignInteractionResults, saveUserFirstConsentedAppId } from '@/lib/session';
|
||||
import { assignInteractionResults } from '@/lib/session';
|
||||
import {
|
||||
encryptUserPassword,
|
||||
generateUserId,
|
||||
|
@ -23,21 +19,10 @@ import assertThat from '@/utils/assert-that';
|
|||
|
||||
import { AnonymousRouter } from '../types';
|
||||
|
||||
export default function sessionRoutes<T extends AnonymousRouter>(router: T, provider: Provider) {
|
||||
router.post('/session', async (ctx, next) => {
|
||||
const {
|
||||
prompt: { name },
|
||||
} = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
|
||||
if (name === 'consent') {
|
||||
ctx.body = { redirectTo: path.join(ctx.request.origin, '/session/consent') };
|
||||
|
||||
return next();
|
||||
}
|
||||
|
||||
throw new RequestError('session.unsupported_prompt_name');
|
||||
});
|
||||
|
||||
export default function usernamePasswordRoutes<T extends AnonymousRouter>(
|
||||
router: T,
|
||||
provider: Provider
|
||||
) {
|
||||
router.post(
|
||||
'/session/sign-in/username-password',
|
||||
koaGuard({
|
||||
|
@ -72,48 +57,6 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
|||
}
|
||||
);
|
||||
|
||||
router.post('/session/consent', async (ctx, next) => {
|
||||
const interaction = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const {
|
||||
session,
|
||||
grantId,
|
||||
params: { client_id },
|
||||
prompt,
|
||||
} = interaction;
|
||||
assertThat(session, 'session.not_found');
|
||||
|
||||
const { accountId } = session;
|
||||
const grant =
|
||||
conditional(grantId && (await provider.Grant.find(grantId))) ??
|
||||
new provider.Grant({ accountId, clientId: String(client_id) });
|
||||
|
||||
await saveUserFirstConsentedAppId(accountId, String(client_id));
|
||||
|
||||
// V2: fulfill missing claims / resources
|
||||
const PromptDetailsBody = object({
|
||||
missingOIDCScope: string().array().optional(),
|
||||
missingResourceScopes: object({}).catchall(string().array()).optional(),
|
||||
});
|
||||
const { missingOIDCScope, missingResourceScopes } = PromptDetailsBody.parse(prompt.details);
|
||||
|
||||
if (missingOIDCScope) {
|
||||
grant.addOIDCScope(missingOIDCScope.join(' '));
|
||||
}
|
||||
|
||||
if (missingResourceScopes) {
|
||||
for (const [indicator, scope] of Object.entries(missingResourceScopes)) {
|
||||
grant.addResourceScope(indicator, scope.join(' '));
|
||||
}
|
||||
}
|
||||
|
||||
const finalGrantId = await grant.save();
|
||||
|
||||
// V2: configure consent
|
||||
await assignInteractionResults(ctx, provider, { consent: { grantId: finalGrantId } }, true);
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.post(
|
||||
'/session/register/username-password',
|
||||
koaGuard({
|
||||
|
@ -162,12 +105,4 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
|||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.delete('/session', async (ctx, next) => {
|
||||
await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const error: LogtoErrorCode = 'oidc.aborted';
|
||||
await assignInteractionResults(ctx, provider, { error });
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
Loading…
Add table
Reference in a new issue