mirror of
https://github.com/logto-io/logto.git
synced 2025-01-06 20:40:08 -05:00
refactor(core): add koaAutoConsent middleware (#5078)
* refactor(core): add koaAutoConsent middleware Add koaAutoConsent middleware to allow auto consent and redirect direactly from the server side. * refactor(core): use koaMount to apply the consent middleware use koaMount to apply the consent middleware * feat(test): update the integration tests update the integration tests * fix(test): update the unit test for consent update the unit test for consent
This commit is contained in:
parent
7a68967267
commit
9669fc92fb
9 changed files with 263 additions and 221 deletions
|
@ -14,13 +14,13 @@ import { type ConnectorLibrary } from './connector.js';
|
|||
export type QuotaLibrary = ReturnType<typeof createQuotaLibrary>;
|
||||
|
||||
const notNumber = (): never => {
|
||||
throw new Error('Only support usage query for numberic quota');
|
||||
throw new Error('Only support usage query for numeric quota');
|
||||
};
|
||||
|
||||
export const createQuotaLibrary = (
|
||||
queries: Queries,
|
||||
cloudConnection: CloudConnectionLibrary,
|
||||
connectorLibraray: ConnectorLibrary
|
||||
connectorLibrary: ConnectorLibrary
|
||||
) => {
|
||||
const {
|
||||
applications: { countNonM2mApplications, countM2mApplications },
|
||||
|
@ -31,7 +31,7 @@ export const createQuotaLibrary = (
|
|||
rolesScopes: { countRolesScopesByRoleId },
|
||||
} = queries;
|
||||
|
||||
const { getLogtoConnectors } = connectorLibraray;
|
||||
const { getLogtoConnectors } = connectorLibrary;
|
||||
|
||||
const tenantUsageQueries: Record<
|
||||
keyof FeatureQuota,
|
||||
|
|
143
packages/core/src/libraries/session.test.ts
Normal file
143
packages/core/src/libraries/session.test.ts
Normal file
|
@ -0,0 +1,143 @@
|
|||
import { type User } from '@logto/schemas';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
import { mockUser } from '#src/__mocks__/user.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { GrantMock, createMockProvider } from '#src/test-utils/oidc-provider.js';
|
||||
import { createContextWithRouteParameters } from '#src/utils/test-utils.js';
|
||||
|
||||
import { consent } from './session.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
|
||||
const grantSave = jest.fn(async (id: string) => id);
|
||||
const grantAddOIDCScope = jest.fn();
|
||||
const grantAddResourceScope = jest.fn();
|
||||
|
||||
class Grant extends GrantMock {
|
||||
static async find(id: string) {
|
||||
return id === 'exists' ? existGrant : undefined;
|
||||
}
|
||||
|
||||
id: string;
|
||||
|
||||
accountId?: string;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.id = generateStandardId();
|
||||
this.save = async () => grantSave(this.id);
|
||||
this.addOIDCScope = grantAddOIDCScope;
|
||||
this.addResourceScope = grantAddResourceScope;
|
||||
}
|
||||
}
|
||||
|
||||
const existGrant = new Grant();
|
||||
|
||||
const userQueries = {
|
||||
findUserById: jest.fn(async (): Promise<User> => mockUser),
|
||||
updateUserById: jest.fn(async (..._args: unknown[]) => ({ id: 'id' })),
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
const queries: Queries = { users: userQueries };
|
||||
const context = createContextWithRouteParameters();
|
||||
|
||||
type Interaction = Awaited<ReturnType<Provider['interactionDetails']>>;
|
||||
|
||||
describe('consent', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const baseInteractionDetails = {
|
||||
session: { accountId: mockUser.id },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: { details: {} },
|
||||
} as unknown as Interaction;
|
||||
|
||||
it('should update with new grantId if not exist', async () => {
|
||||
const provider = createMockProvider(jest.fn().mockResolvedValue(baseInteractionDetails), Grant);
|
||||
await consent(context, provider, queries, baseInteractionDetails);
|
||||
|
||||
expect(grantSave).toHaveBeenCalled();
|
||||
|
||||
expect(provider.interactionResult).toHaveBeenCalledWith(
|
||||
context.req,
|
||||
context.res,
|
||||
{
|
||||
...baseInteractionDetails.result,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
consent: { grantId: expect.any(String) },
|
||||
},
|
||||
{
|
||||
mergeWithLastSubmission: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should update with existing grantId if exist', async () => {
|
||||
const interactionDetails = {
|
||||
...baseInteractionDetails,
|
||||
grantId: 'exists',
|
||||
} as unknown as Interaction;
|
||||
|
||||
const provider = createMockProvider(jest.fn().mockResolvedValue(interactionDetails), Grant);
|
||||
|
||||
await consent(context, provider, queries, interactionDetails);
|
||||
|
||||
expect(grantSave).toHaveBeenCalled();
|
||||
|
||||
expect(provider.interactionResult).toHaveBeenCalledWith(
|
||||
context.req,
|
||||
context.res,
|
||||
{
|
||||
...baseInteractionDetails.result,
|
||||
consent: { grantId: existGrant.id },
|
||||
},
|
||||
{
|
||||
mergeWithLastSubmission: true,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should save first consented app id', async () => {
|
||||
userQueries.findUserById.mockImplementationOnce(async () => ({
|
||||
...mockUser,
|
||||
applicationId: null,
|
||||
}));
|
||||
|
||||
const provider = createMockProvider(jest.fn().mockResolvedValue(baseInteractionDetails), Grant);
|
||||
await consent(context, provider, queries, baseInteractionDetails);
|
||||
|
||||
expect(userQueries.updateUserById).toHaveBeenCalledWith(mockUser.id, {
|
||||
applicationId: baseInteractionDetails.params.client_id,
|
||||
});
|
||||
});
|
||||
|
||||
it('should grant missing scopes', async () => {
|
||||
const interactionDetails = {
|
||||
...baseInteractionDetails,
|
||||
prompt: {
|
||||
details: {
|
||||
missingOIDCScope: ['openid', 'profile'],
|
||||
missingResourceScopes: {
|
||||
resource1: ['resource1_scope1', 'resource1_scope2'],
|
||||
resource2: ['resource2_scope1'],
|
||||
},
|
||||
},
|
||||
},
|
||||
} as unknown as Interaction;
|
||||
|
||||
const provider = createMockProvider(jest.fn().mockResolvedValue(interactionDetails), Grant);
|
||||
await consent(context, provider, queries, interactionDetails);
|
||||
|
||||
expect(grantAddOIDCScope).toHaveBeenCalledWith('openid profile');
|
||||
expect(grantAddResourceScope).toHaveBeenCalledWith(
|
||||
'resource1',
|
||||
'resource1_scope1 resource1_scope2'
|
||||
);
|
||||
expect(grantAddResourceScope).toHaveBeenCalledWith('resource2', 'resource2_scope1');
|
||||
});
|
||||
});
|
|
@ -1,10 +1,13 @@
|
|||
import { conditional } from '@silverhand/essentials';
|
||||
import type { Context } from 'koa';
|
||||
import type { InteractionResults } from 'oidc-provider';
|
||||
import type Provider from 'oidc-provider';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
export const assignInteractionResults = async (
|
||||
const updateInteractionResult = async (
|
||||
ctx: Context,
|
||||
provider: Provider,
|
||||
result: InteractionResults,
|
||||
|
@ -16,7 +19,7 @@ export const assignInteractionResults = async (
|
|||
// refer to: https://github.com/panva/node-oidc-provider/blob/c243bf6b6663c41ff3e75c09b95fb978eba87381/lib/actions/authorization/interactions.js#L106
|
||||
const details = merge ? await provider.interactionDetails(ctx.req, ctx.res) : undefined;
|
||||
|
||||
const redirectTo = await provider.interactionResult(
|
||||
return provider.interactionResult(
|
||||
ctx.req,
|
||||
ctx.res,
|
||||
{
|
||||
|
@ -28,10 +31,20 @@ export const assignInteractionResults = async (
|
|||
mergeWithLastSubmission: merge,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const assignInteractionResults = async (
|
||||
ctx: Context,
|
||||
provider: Provider,
|
||||
result: InteractionResults,
|
||||
merge = false
|
||||
) => {
|
||||
const redirectTo = await updateInteractionResult(ctx, provider, result, merge);
|
||||
|
||||
ctx.body = { redirectTo };
|
||||
};
|
||||
|
||||
export const saveUserFirstConsentedAppId = async (
|
||||
const saveUserFirstConsentedAppId = async (
|
||||
queries: Queries,
|
||||
userId: string,
|
||||
applicationId: string
|
||||
|
@ -44,3 +57,49 @@ export const saveUserFirstConsentedAppId = async (
|
|||
await updateUserById(userId, { applicationId });
|
||||
}
|
||||
};
|
||||
|
||||
export const consent = async (
|
||||
ctx: Context,
|
||||
provider: Provider,
|
||||
queries: Queries,
|
||||
interactionDetails: Awaited<ReturnType<Provider['interactionDetails']>>
|
||||
) => {
|
||||
const {
|
||||
session,
|
||||
grantId,
|
||||
params: { client_id },
|
||||
prompt,
|
||||
} = interactionDetails;
|
||||
|
||||
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(queries, accountId, String(client_id));
|
||||
|
||||
// Fulfill missing claims / resources
|
||||
const PromptDetailsBody = z.object({
|
||||
missingOIDCScope: z.string().array().optional(),
|
||||
missingResourceScopes: z.object({}).catchall(z.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();
|
||||
|
||||
// Configure consent
|
||||
return updateInteractionResult(ctx, provider, { consent: { grantId: finalGrantId } }, true);
|
||||
};
|
||||
|
|
28
packages/core/src/middleware/koa-auto-consent.ts
Normal file
28
packages/core/src/middleware/koa-auto-consent.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { type MiddlewareType } from 'koa';
|
||||
import { type IRouterParamContext } from 'koa-router';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
import { consent } from '#src/libraries/session.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
|
||||
/**
|
||||
* Automatically consent for the first party apps.
|
||||
*/
|
||||
export default function koaAutoConsent<StateT, ContextT extends IRouterParamContext, ResponseBodyT>(
|
||||
provider: Provider,
|
||||
query: Queries
|
||||
): MiddlewareType<StateT, ContextT, ResponseBodyT> {
|
||||
return async (ctx, next) => {
|
||||
const shouldAutoConsent = true;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Update later. Third party app should not auto consent
|
||||
if (!shouldAutoConsent) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const interactionDetails = await provider.interactionDetails(ctx.req, ctx.res);
|
||||
const redirectTo = await consent(ctx, provider, query, interactionDetails);
|
||||
|
||||
ctx.redirect(redirectTo);
|
||||
};
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
import type { User } from '@logto/schemas';
|
||||
import { createMockUtils } from '@logto/shared/esm';
|
||||
|
||||
import { mockUser } from '#src/__mocks__/index.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import { createMockProvider, GrantMock } from '#src/test-utils/oidc-provider.js';
|
||||
import type { Partial2 } from '#src/test-utils/tenant.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
import { interactionPrefix } from './const.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const grantSave = jest.fn(async () => 'finalGrantId');
|
||||
const grantAddOIDCScope = jest.fn();
|
||||
const grantAddResourceScope = jest.fn();
|
||||
|
||||
class Grant extends GrantMock {
|
||||
static async find(id: string) {
|
||||
return id === 'exists' ? new Grant() : undefined;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.save = grantSave;
|
||||
this.addOIDCScope = grantAddOIDCScope;
|
||||
this.addResourceScope = grantAddResourceScope;
|
||||
}
|
||||
}
|
||||
|
||||
const userQueries = {
|
||||
findUserById: jest.fn(async (): Promise<User> => mockUser),
|
||||
updateUserById: jest.fn(async (..._args: unknown[]) => ({ id: 'id' })),
|
||||
};
|
||||
const { findUserById, updateUserById } = userQueries;
|
||||
|
||||
// @ts-expect-error
|
||||
const queries: Partial2<Queries> = { users: userQueries };
|
||||
|
||||
const { assignInteractionResults } = await mockEsmWithActual('#src/libraries/session.js', () => ({
|
||||
assignInteractionResults: jest.fn(),
|
||||
}));
|
||||
|
||||
const { default: interactionRoutes } = await import('./index.js');
|
||||
|
||||
describe('interaction -> consent', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const baseInteractionDetails = {
|
||||
session: { accountId: mockUser.id },
|
||||
params: { client_id: 'clientId' },
|
||||
prompt: { details: {} },
|
||||
};
|
||||
|
||||
it('with empty details and reusing old grant', async () => {
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: interactionRoutes,
|
||||
tenantContext: new MockTenant(
|
||||
createMockProvider(jest.fn().mockResolvedValue(baseInteractionDetails), Grant),
|
||||
queries
|
||||
),
|
||||
});
|
||||
|
||||
await sessionRequest.post(`${interactionPrefix}/consent`);
|
||||
expect(grantSave).toHaveBeenCalled();
|
||||
expect(assignInteractionResults).toBeCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('with empty details and creating new grant', async () => {
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: interactionRoutes,
|
||||
tenantContext: new MockTenant(
|
||||
createMockProvider(
|
||||
jest.fn().mockResolvedValue({
|
||||
...baseInteractionDetails,
|
||||
grantId: 'exists',
|
||||
}),
|
||||
Grant
|
||||
),
|
||||
queries
|
||||
),
|
||||
});
|
||||
|
||||
await sessionRequest.post(`${interactionPrefix}/consent`);
|
||||
|
||||
expect(grantSave).toHaveBeenCalled();
|
||||
expect(assignInteractionResults).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('should save application id when the user first consented', async () => {
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: interactionRoutes,
|
||||
tenantContext: new MockTenant(
|
||||
createMockProvider(
|
||||
jest.fn().mockResolvedValue({
|
||||
...baseInteractionDetails,
|
||||
prompt: {
|
||||
name: 'consent',
|
||||
details: {},
|
||||
reasons: ['consent_prompt', 'native_client_prompt'],
|
||||
},
|
||||
}),
|
||||
Grant
|
||||
),
|
||||
queries
|
||||
),
|
||||
});
|
||||
|
||||
findUserById.mockImplementationOnce(async () => ({ ...mockUser, applicationId: null }));
|
||||
|
||||
await sessionRequest.post(`${interactionPrefix}/consent`);
|
||||
expect(updateUserById).toHaveBeenCalledWith(mockUser.id, { applicationId: 'clientId' });
|
||||
});
|
||||
|
||||
it('missingOIDCScope and missingResourceScopes', async () => {
|
||||
const sessionRequest = createRequester({
|
||||
anonymousRoutes: interactionRoutes,
|
||||
tenantContext: new MockTenant(
|
||||
createMockProvider(
|
||||
jest.fn().mockResolvedValue({
|
||||
...baseInteractionDetails,
|
||||
prompt: {
|
||||
details: {
|
||||
missingOIDCScope: ['scope1', 'scope2'],
|
||||
missingResourceScopes: {
|
||||
resource1: ['scope1', 'scope2'],
|
||||
resource2: ['scope3'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
Grant
|
||||
),
|
||||
queries
|
||||
),
|
||||
});
|
||||
|
||||
await sessionRequest.post(`${interactionPrefix}/consent`);
|
||||
expect(grantAddOIDCScope).toHaveBeenCalledWith('scope1 scope2');
|
||||
expect(grantAddResourceScope).toHaveBeenCalledWith('resource1', 'scope1 scope2');
|
||||
expect(grantAddResourceScope).toHaveBeenCalledWith('resource2', 'scope3');
|
||||
expect(assignInteractionResults).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
consent: { grantId: 'finalGrantId' },
|
||||
}),
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,11 +1,8 @@
|
|||
import { conditional } from '@silverhand/essentials';
|
||||
import type Router from 'koa-router';
|
||||
import { type IRouterParamContext } from 'koa-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { assignInteractionResults, saveUserFirstConsentedAppId } from '#src/libraries/session.js';
|
||||
import { consent } from '#src/libraries/session.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
import { interactionPrefix } from './const.js';
|
||||
import type { WithInteractionDetailsContext } from './middleware/koa-interaction-details.js';
|
||||
|
@ -17,44 +14,9 @@ export default function consentRoutes<T extends IRouterParamContext>(
|
|||
router.post(`${interactionPrefix}/consent`, async (ctx, next) => {
|
||||
const { interactionDetails } = ctx;
|
||||
|
||||
const {
|
||||
session,
|
||||
grantId,
|
||||
params: { client_id },
|
||||
prompt,
|
||||
} = interactionDetails;
|
||||
const redirectTo = await consent(ctx, provider, queries, interactionDetails);
|
||||
|
||||
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(queries, accountId, String(client_id));
|
||||
|
||||
// V2: fulfill missing claims / resources
|
||||
const PromptDetailsBody = z.object({
|
||||
missingOIDCScope: z.string().array().optional(),
|
||||
missingResourceScopes: z.object({}).catchall(z.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);
|
||||
ctx.body = { redirectTo };
|
||||
|
||||
return next();
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import { AdminApps, EnvSet, UserApps } from '#src/env-set/index.js';
|
|||
import { createCloudConnectionLibrary } from '#src/libraries/cloud-connection.js';
|
||||
import { createConnectorLibrary } from '#src/libraries/connector.js';
|
||||
import { createLogtoConfigLibrary } from '#src/libraries/logto-config.js';
|
||||
import koaAutoConsent from '#src/middleware/koa-auto-consent.js';
|
||||
import koaConnectorErrorHandler from '#src/middleware/koa-connector-error-handler.js';
|
||||
import koaConsoleRedirectProxy from '#src/middleware/koa-console-redirect-proxy.js';
|
||||
import koaErrorHandler from '#src/middleware/koa-error-handler.js';
|
||||
|
@ -23,6 +24,7 @@ import koaSlonikErrorHandler from '#src/middleware/koa-slonik-error-handler.js';
|
|||
import koaSpaProxy from '#src/middleware/koa-spa-proxy.js';
|
||||
import koaSpaSessionGuard from '#src/middleware/koa-spa-session-guard.js';
|
||||
import initOidc from '#src/oidc/init.js';
|
||||
import { routes } from '#src/routes/consts.js';
|
||||
import initApis from '#src/routes/init.js';
|
||||
import initMeApis from '#src/routes-me/init.js';
|
||||
import BasicSentinel from '#src/sentinel/basic-sentinel.js';
|
||||
|
@ -137,7 +139,13 @@ export default class Tenant implements TenantContext {
|
|||
}
|
||||
|
||||
// Mount UI
|
||||
app.use(compose([koaSpaSessionGuard(provider, queries), koaSpaProxy(mountedApps)]));
|
||||
app.use(
|
||||
compose([
|
||||
koaSpaSessionGuard(provider, queries),
|
||||
mount(`${routes.consent}`, koaAutoConsent(provider, queries)),
|
||||
koaSpaProxy(mountedApps),
|
||||
])
|
||||
);
|
||||
|
||||
this.app = app;
|
||||
this.provider = provider;
|
||||
|
|
|
@ -42,6 +42,8 @@ export const createMockProvider = (
|
|||
interactionDetails ?? (async () => ({ params: {}, jti: 'jti', client_id: 'mockApplicationId' }))
|
||||
);
|
||||
|
||||
jest.spyOn(provider, 'interactionResult').mockImplementation(async () => 'redirectTo');
|
||||
|
||||
if (Grant) {
|
||||
Sinon.stub(provider, 'Grant').value(Grant);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import { assert } from '@silverhand/essentials';
|
|||
import type { Got } from 'got';
|
||||
import { got } from 'got';
|
||||
|
||||
import { consent, submitInteraction } from '#src/api/index.js';
|
||||
import { submitInteraction } from '#src/api/index.js';
|
||||
import { demoAppRedirectUri, logtoUrl } from '#src/constants.js';
|
||||
|
||||
import { MemoryStorage } from './storage.js';
|
||||
|
@ -162,10 +162,19 @@ export default class MockClient {
|
|||
assert(this.interactionCookie, new Error('Session not found'));
|
||||
assert(this.interactionCookie.includes('_session.sig'), new Error('Session not found'));
|
||||
|
||||
const { redirectTo } = await consent(this.api, this.interactionCookie);
|
||||
const consentResponse = await got.get(`${this.config.endpoint}/consent`, {
|
||||
headers: {
|
||||
cookie: this.interactionCookie,
|
||||
},
|
||||
followRedirect: false,
|
||||
});
|
||||
|
||||
// Note: should redirect to oidc auth endpoint
|
||||
assert(redirectTo.startsWith(`${this.config.endpoint}/oidc/auth`), new Error('Consent failed'));
|
||||
// Consent page should auto consent and redirect to auth endpoint
|
||||
const redirectTo = consentResponse.headers.location;
|
||||
|
||||
if (!redirectTo?.startsWith(`${this.config.endpoint}/oidc/auth`)) {
|
||||
throw new Error('Consent failed');
|
||||
}
|
||||
|
||||
const authCodeResponse = await got.get(redirectTo, {
|
||||
headers: {
|
||||
|
|
Loading…
Reference in a new issue