0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-02-03 21:48:55 -05:00

Merge pull request #2423 from logto-io/charles-change-session-health-validation-method

refactor(core): change session health validation method for session APIs
This commit is contained in:
Charles Zhao 2022-11-11 19:47:06 +08:00 committed by GitHub
commit e16bb06c26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 37 additions and 73 deletions

View file

@ -1,10 +1,10 @@
import { conditional } from '@silverhand/essentials';
import { getUnixTime } from 'date-fns'; import { getUnixTime } from 'date-fns';
import type { Context } from 'koa'; import type { Context } from 'koa';
import type { InteractionResults, Provider } from 'oidc-provider'; import type { InteractionResults, Provider } from 'oidc-provider';
import RequestError from '@/errors/RequestError'; import RequestError from '@/errors/RequestError';
import { findUserById, updateUserById } from '@/queries/user'; import { findUserById, updateUserById } from '@/queries/user';
import assertThat from '@/utils/assert-that';
export const assignInteractionResults = async ( export const assignInteractionResults = async (
ctx: Context, ctx: Context,
@ -17,32 +17,20 @@ export const assignInteractionResults = async (
// have to do it manually // have to do it manually
// refer to: https://github.com/panva/node-oidc-provider/blob/c243bf6b6663c41ff3e75c09b95fb978eba87381/lib/actions/authorization/interactions.js#L106 // 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 details = merge ? await provider.interactionDetails(ctx.req, ctx.res) : undefined;
const ts = getUnixTime(new Date());
const mergedResult = {
// Merge with current result
...details?.result,
...result,
};
const redirectTo = await provider.interactionResult( const redirectTo = await provider.interactionResult(
ctx.req, ctx.req,
ctx.res, ctx.res,
{ {
...mergedResult, // Merge with current result
...conditional( ...details?.result,
mergedResult.login && { ...result,
login: {
...mergedResult.login,
// Update ts(timestamp) if the accountId has been set in result
ts: result.login?.accountId ? ts : mergedResult.login.ts,
},
}
),
}, },
{ {
mergeWithLastSubmission: merge, mergeWithLastSubmission: merge,
} }
); );
ctx.body = { redirectTo, ts }; ctx.body = { redirectTo };
}; };
export const checkSessionHealth = async ( export const checkSessionHealth = async (
@ -50,23 +38,24 @@ export const checkSessionHealth = async (
provider: Provider, provider: Provider,
tolerance = 10 * 60 // 10 mins tolerance = 10 * 60 // 10 mins
) => { ) => {
const { result } = await provider.interactionDetails(ctx.req, ctx.res); const { accountId, loginTs } = await provider.Session.get(ctx);
if (!result?.login?.accountId) { assertThat(
throw new RequestError('auth.unauthorized'); accountId,
} new RequestError({ code: 'auth.unauthorized', id: accountId, status: 401 })
);
if (!result.login.ts || result.login.ts < getUnixTime(new Date()) - tolerance) { if (!loginTs || loginTs < getUnixTime(new Date()) - tolerance) {
const { passwordEncrypted, primaryPhone, primaryEmail } = await findUserById( const { passwordEncrypted, primaryPhone, primaryEmail } = await findUserById(accountId);
result.login.accountId
);
// No authenticated method configured for this user. Pass! // No authenticated method configured for this user. Pass!
if (!passwordEncrypted && !primaryPhone && !primaryEmail) { if (!passwordEncrypted && !primaryPhone && !primaryEmail) {
return; return;
} }
throw new RequestError('auth.require_re_authentication'); throw new RequestError({ code: 'auth.require_re_authentication', status: 422 });
} }
return accountId;
}; };
export const saveUserFirstConsentedAppId = async (userId: string, applicationId: string) => { export const saveUserFirstConsentedAppId = async (userId: string, applicationId: string) => {

View file

@ -89,8 +89,7 @@ describe('session -> continueRoutes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -122,8 +121,7 @@ describe('session -> continueRoutes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -158,8 +156,7 @@ describe('session -> continueRoutes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -191,8 +188,7 @@ describe('session -> continueRoutes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });

View file

@ -111,8 +111,7 @@ describe('session -> password routes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -128,8 +127,7 @@ describe('session -> password routes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -145,8 +143,7 @@ describe('session -> password routes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -175,8 +172,7 @@ describe('session -> password routes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: 'user1' } }),
expect.objectContaining({ login: { accountId: 'user1', ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
jest.useRealTimers(); jest.useRealTimers();

View file

@ -396,8 +396,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
expect.objectContaining({ expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment login: { accountId: mockUser.id },
login: { accountId: mockUser.id, ts: expect.any(Number) },
}), }),
expect.anything() expect.anything()
); );
@ -419,8 +418,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
expect.objectContaining({ expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment login: { accountId: mockUser.id },
login: { accountId: mockUser.id, ts: expect.any(Number) },
}), }),
expect.anything() expect.anything()
); );
@ -562,8 +560,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
expect.objectContaining({ expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment login: { accountId: mockUser.id },
login: { accountId: mockUser.id, ts: expect.any(Number) },
}), }),
expect.anything() expect.anything()
); );
@ -587,8 +584,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
expect.objectContaining({ expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment login: { accountId: mockUser.id },
login: { accountId: mockUser.id, ts: expect.any(Number) },
}), }),
expect.anything() expect.anything()
); );
@ -698,8 +694,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
expect.objectContaining({ expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment login: { accountId: 'user1' },
login: { accountId: 'user1', ts: expect.any(Number) },
}), }),
expect.anything() expect.anything()
); );
@ -721,8 +716,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
expect.objectContaining({ expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment login: { accountId: 'user1' },
login: { accountId: 'user1', ts: expect.any(Number) },
}), }),
expect.anything() expect.anything()
); );
@ -828,8 +822,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
expect.objectContaining({ expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment login: { accountId: 'user1' },
login: { accountId: 'user1', ts: expect.any(Number) },
}), }),
expect.anything() expect.anything()
); );
@ -851,8 +844,7 @@ describe('session -> passwordlessRoutes', () => {
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
expect.objectContaining({ expect.objectContaining({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment login: { accountId: 'user1' },
login: { accountId: 'user1', ts: expect.any(Number) },
}), }),
expect.anything() expect.anything()
); );

View file

@ -1,4 +1,3 @@
/* eslint-disable max-lines */
import { ConnectorType } from '@logto/connector-kit'; import { ConnectorType } from '@logto/connector-kit';
import type { User } from '@logto/schemas'; import type { User } from '@logto/schemas';
import { SignUpIdentifier } from '@logto/schemas'; import { SignUpIdentifier } from '@logto/schemas';
@ -234,8 +233,7 @@ describe('session -> socialRoutes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -318,8 +316,7 @@ describe('session -> socialRoutes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: 'user1' } }),
expect.objectContaining({ login: { accountId: 'user1', ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -353,8 +350,7 @@ describe('session -> socialRoutes', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: 'user1' } }),
expect.objectContaining({ login: { accountId: 'user1', ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });
@ -368,10 +364,7 @@ describe('session -> socialRoutes', () => {
}); });
it('throw error if result parsing fails', async () => { it('throw error if result parsing fails', async () => {
interactionDetails.mockResolvedValueOnce({ interactionDetails.mockResolvedValueOnce({ result: { login: { accountId: mockUser.id } } });
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
result: { login: { accountId: mockUser.id, ts: expect.any(Number) } },
});
const response = await sessionRequest const response = await sessionRequest
.post(`${registerRoute}`) .post(`${registerRoute}`)
.send({ connectorId: 'connectorId' }); .send({ connectorId: 'connectorId' });
@ -443,4 +436,3 @@ describe('session -> socialRoutes', () => {
}); });
}); });
}); });
/* eslint-enable max-lines */

View file

@ -121,8 +121,7 @@ describe('signInWithPassword()', () => {
expect(interactionResult).toHaveBeenCalledWith( expect(interactionResult).toHaveBeenCalledWith(
expect.anything(), expect.anything(),
expect.anything(), expect.anything(),
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment expect.objectContaining({ login: { accountId: mockUser.id } }),
expect.objectContaining({ login: { accountId: mockUser.id, ts: expect.any(Number) } }),
expect.anything() expect.anything()
); );
}); });