mirror of
https://github.com/logto-io/logto.git
synced 2025-02-03 21:48:55 -05:00
feat(core): separate sessionRoutes by flow types (#235)
* feat(core): separate sessionRoute by use cases as well as flow types * feat(core): fix API calls in UI accordingly * feat(core): fix lint errors
This commit is contained in:
parent
011f073efe
commit
c99c6b55aa
3 changed files with 133 additions and 73 deletions
|
@ -1,9 +1,10 @@
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
import { LogtoErrorCode } from '@logto/phrases';
|
import { LogtoErrorCode } from '@logto/phrases';
|
||||||
import { conditional } from '@silverhand/essentials';
|
import { conditional } from '@silverhand/essentials';
|
||||||
import { Provider, errors } from 'oidc-provider';
|
import { Provider } from 'oidc-provider';
|
||||||
import { object, string } from 'zod';
|
import { object, string } from 'zod';
|
||||||
|
|
||||||
import RequestError from '@/errors/RequestError';
|
|
||||||
import {
|
import {
|
||||||
registerWithSocial,
|
registerWithSocial,
|
||||||
registerWithEmailAndPasscode,
|
registerWithEmailAndPasscode,
|
||||||
|
@ -27,61 +28,86 @@ import assertThat from '@/utils/assert-that';
|
||||||
import { AnonymousRouter } from './types';
|
import { AnonymousRouter } from './types';
|
||||||
|
|
||||||
export default function sessionRoutes<T extends AnonymousRouter>(router: T, provider: Provider) {
|
export default function sessionRoutes<T extends AnonymousRouter>(router: T, provider: Provider) {
|
||||||
router.post(
|
router.post('/session', async (ctx, next) => {
|
||||||
'/session',
|
|
||||||
koaGuard({
|
|
||||||
body: object({
|
|
||||||
username: string().optional(),
|
|
||||||
password: string().optional(),
|
|
||||||
email: string().optional(),
|
|
||||||
phone: string().optional(),
|
|
||||||
code: string().optional(),
|
|
||||||
connectorId: string().optional(),
|
|
||||||
state: string().optional(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
async (ctx, next) => {
|
|
||||||
const interaction = await provider.interactionDetails(ctx.req, ctx.res);
|
|
||||||
const {
|
const {
|
||||||
// Interaction's JWT identity: jti
|
|
||||||
// https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#user-flows
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7519#section-4.1.7
|
|
||||||
jti,
|
|
||||||
prompt: { name },
|
prompt: { name },
|
||||||
result,
|
} = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
} = interaction;
|
|
||||||
|
|
||||||
if (name === 'consent') {
|
if (name === 'consent') {
|
||||||
ctx.body = { redirectTo: ctx.request.origin + '/session/consent' };
|
ctx.body = { redirectTo: path.join(ctx.request.origin, '/session/consent') };
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (name === 'login') {
|
router.post(
|
||||||
const { username, password, email, phone, code, connectorId, state } = ctx.guard.body;
|
'/session/sign-in/username-password',
|
||||||
|
koaGuard({ body: object({ username: string(), password: string() }) }),
|
||||||
if (connectorId && state && !code) {
|
async (ctx, next) => {
|
||||||
await assignRedirectUrlForSocial(ctx, connectorId, state);
|
const { username, password } = ctx.guard.body;
|
||||||
} else if (connectorId && code) {
|
|
||||||
await signInWithSocial(ctx, provider, { connectorId, code, result });
|
|
||||||
} else if (email && !code) {
|
|
||||||
await sendSignInWithEmailPasscode(ctx, jti, email);
|
|
||||||
} else if (email && code) {
|
|
||||||
await signInWithEmailAndPasscode(ctx, provider, { jti, email, code });
|
|
||||||
} else if (phone && !code) {
|
|
||||||
await sendSignInWithPhonePasscode(ctx, jti, phone);
|
|
||||||
} else if (phone && code) {
|
|
||||||
await signInWithPhoneAndPasscode(ctx, provider, { jti, phone, code });
|
|
||||||
} else if (username && password) {
|
|
||||||
await signInWithUsernameAndPassword(ctx, provider, username, password);
|
await signInWithUsernameAndPassword(ctx, provider, username, password);
|
||||||
} else {
|
|
||||||
throw new RequestError('session.insufficient_info');
|
return next();
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/session/sign-in/passwordless/phone',
|
||||||
|
koaGuard({ body: object({ phone: string(), code: string().optional() }) }),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
|
const { phone, code } = ctx.guard.body;
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
await sendSignInWithPhonePasscode(ctx, jti, phone);
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new errors.InvalidRequest(`Prompt not supported: ${name}`);
|
await signInWithPhoneAndPasscode(ctx, provider, { jti, phone, code });
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/session/sign-in/passwordless/email',
|
||||||
|
koaGuard({ body: object({ email: string(), code: string().optional() }) }),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
|
const { email, code } = ctx.guard.body;
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
await sendSignInWithEmailPasscode(ctx, jti, email);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
await signInWithEmailAndPasscode(ctx, provider, { jti, email, code });
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/session/sign-in/social',
|
||||||
|
koaGuard({
|
||||||
|
body: object({ connectorId: string(), code: string().optional(), state: string() }),
|
||||||
|
}),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { result } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
|
const { connectorId, code, state } = ctx.guard.body;
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
assertThat(state, 'session.insufficient_info');
|
||||||
|
await assignRedirectUrlForSocial(ctx, connectorId, state);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
await signInWithSocial(ctx, provider, { connectorId, code, result });
|
||||||
|
|
||||||
|
return next();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -127,41 +153,75 @@ export default function sessionRoutes<T extends AnonymousRouter>(router: T, prov
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
'/session/register',
|
'/session/register/username-password',
|
||||||
|
koaGuard({ body: object({ username: string(), password: string() }) }),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { username, password } = ctx.guard.body;
|
||||||
|
await registerWithUsernameAndPassword(ctx, provider, username, password);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/session/register/passwordless/phone',
|
||||||
|
koaGuard({ body: object({ phone: string(), code: string().optional() }) }),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
|
const { phone, code } = ctx.guard.body;
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
await sendPasscodeToPhone(ctx, jti, phone);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
await registerWithPhoneAndPasscode(ctx, provider, { jti, phone, code });
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/session/register/passwordless/email',
|
||||||
|
koaGuard({ body: object({ email: string(), code: string().optional() }) }),
|
||||||
|
async (ctx, next) => {
|
||||||
|
const { jti } = await provider.interactionDetails(ctx.req, ctx.res);
|
||||||
|
const { email, code } = ctx.guard.body;
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
await sendPasscodeToEmail(ctx, jti, email);
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
await registerWithEmailAndPasscode(ctx, provider, { jti, email, code });
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/session/register/social',
|
||||||
koaGuard({
|
koaGuard({
|
||||||
body: object({
|
body: object({
|
||||||
username: string().min(3).optional(),
|
connectorId: string(),
|
||||||
password: string().min(6).optional(),
|
|
||||||
email: string().optional(),
|
|
||||||
phone: string().optional(),
|
|
||||||
code: string().optional(),
|
code: string().optional(),
|
||||||
connectorId: string().optional(),
|
|
||||||
state: string().optional(),
|
state: string().optional(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
async (ctx, next) => {
|
async (ctx, next) => {
|
||||||
const interaction = await provider.interactionDetails(ctx.req, ctx.res);
|
const { connectorId, code, state } = ctx.guard.body;
|
||||||
const { jti } = interaction;
|
|
||||||
const { username, password, email, phone, code, connectorId, state } = ctx.guard.body;
|
|
||||||
|
|
||||||
if (connectorId && state && !code) {
|
if (!code) {
|
||||||
|
assertThat(state, 'session.insufficient_info');
|
||||||
await assignRedirectUrlForSocial(ctx, connectorId, state);
|
await assignRedirectUrlForSocial(ctx, connectorId, state);
|
||||||
} else if (connectorId && state && code) {
|
|
||||||
await registerWithSocial(ctx, provider, { connectorId, code });
|
return next();
|
||||||
} else if (email && !code) {
|
|
||||||
await sendPasscodeToEmail(ctx, jti, email);
|
|
||||||
} else if (email && code) {
|
|
||||||
await registerWithEmailAndPasscode(ctx, provider, { jti, email, code });
|
|
||||||
} else if (phone && !code) {
|
|
||||||
await sendPasscodeToPhone(ctx, jti, phone);
|
|
||||||
} else if (phone && code) {
|
|
||||||
await registerWithPhoneAndPasscode(ctx, provider, { jti, phone, code });
|
|
||||||
} else if (username && password) {
|
|
||||||
await registerWithUsernameAndPassword(ctx, provider, username, password);
|
|
||||||
} else {
|
|
||||||
throw new RequestError('session.insufficient_info');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await registerWithSocial(ctx, provider, { connectorId, code });
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const register = async (username: string, password: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return ky
|
return ky
|
||||||
.post('/api/session/register', {
|
.post('/api/session/register/username-password', {
|
||||||
json: {
|
json: {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const signInBasic = async (username: string, password: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
return ky
|
return ky
|
||||||
.post('/api/session', {
|
.post('/api/session/sign-in/username-password', {
|
||||||
json: {
|
json: {
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
|
Loading…
Add table
Reference in a new issue