0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-16 20:26:19 -05:00

feat(core): provide /me APIs for user password and verification code in cloud AC (#3192)

This commit is contained in:
Charles Zhao 2023-02-24 16:01:14 +08:00 committed by GitHub
parent 586cc96113
commit 9311f4cf93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 139 additions and 38 deletions

View file

@ -1,8 +1,4 @@
import { import { adminTenantId, getManagementApiResourceIndicator } from '@logto/schemas';
adminTenantId,
arbitraryObjectGuard,
getManagementApiResourceIndicator,
} from '@logto/schemas';
import Koa from 'koa'; import Koa from 'koa';
import Router from 'koa-router'; import Router from 'koa-router';
@ -11,18 +7,18 @@ import RequestError from '#src/errors/RequestError/index.js';
import type { WithAuthContext } from '#src/middleware/koa-auth/index.js'; import type { WithAuthContext } from '#src/middleware/koa-auth/index.js';
import koaAuth from '#src/middleware/koa-auth/index.js'; import koaAuth from '#src/middleware/koa-auth/index.js';
import koaCors from '#src/middleware/koa-cors.js'; import koaCors from '#src/middleware/koa-cors.js';
import koaGuard from '#src/middleware/koa-guard.js';
import type TenantContext from '#src/tenants/TenantContext.js'; import type TenantContext from '#src/tenants/TenantContext.js';
import assertThat from '#src/utils/assert-that.js'; import assertThat from '#src/utils/assert-that.js';
import socialRoutes from './social.js'; import socialRoutes from './social.js';
import userRoutes from './user.js';
import verificationCodeRoutes from './verification-code.js';
export default function initMeApis(tenant: TenantContext): Koa { export default function initMeApis(tenant: TenantContext): Koa {
if (tenant.id !== adminTenantId) { if (tenant.id !== adminTenantId) {
throw new Error('`/me` routes should only be initialized in the admin tenant.'); throw new Error('`/me` routes should only be initialized in the admin tenant.');
} }
const { findUserById, updateUserById } = tenant.queries.users;
const meRouter = new Router<unknown, WithAuthContext>(); const meRouter = new Router<unknown, WithAuthContext>();
meRouter.use( meRouter.use(
@ -37,38 +33,9 @@ export default function initMeApis(tenant: TenantContext): Koa {
} }
); );
meRouter.get('/custom-data', async (ctx, next) => { userRoutes(meRouter, tenant);
const { id: userId } = ctx.auth;
const user = await findUserById(userId);
ctx.body = user.customData;
return next();
});
meRouter.patch(
'/custom-data',
koaGuard({
body: arbitraryObjectGuard,
response: arbitraryObjectGuard,
}),
async (ctx, next) => {
const { id: userId } = ctx.auth;
const { body: customData } = ctx.guard;
await findUserById(userId);
const user = await updateUserById(userId, {
customData,
});
ctx.body = user.customData;
return next();
}
);
socialRoutes(meRouter, tenant); socialRoutes(meRouter, tenant);
verificationCodeRoutes(meRouter, tenant);
const meApp = new Koa(); const meApp = new Koa();
meApp.use(koaCors(EnvSet.values.cloudUrlSet)); meApp.use(koaCors(EnvSet.values.cloudUrlSet));

View file

@ -0,0 +1,85 @@
import { passwordRegEx } from '@logto/core-kit';
import { arbitraryObjectGuard } from '@logto/schemas';
import { object, string } from 'zod';
import RequestError from '#src/errors/RequestError/index.js';
import { encryptUserPassword, verifyUserPassword } from '#src/libraries/user.js';
import koaGuard from '#src/middleware/koa-guard.js';
import assertThat from '#src/utils/assert-that.js';
import type { RouterInitArgs } from '../routes/types.js';
import type { AuthedMeRouter } from './types.js';
export default function userRoutes<T extends AuthedMeRouter>(
...[router, tenant]: RouterInitArgs<T>
) {
const { findUserById, updateUserById } = tenant.queries.users;
router.get('/custom-data', async (ctx, next) => {
const { id: userId } = ctx.auth;
const user = await findUserById(userId);
ctx.body = user.customData;
return next();
});
router.patch(
'/custom-data',
koaGuard({
body: arbitraryObjectGuard,
response: arbitraryObjectGuard,
}),
async (ctx, next) => {
const { id: userId } = ctx.auth;
const { body: customData } = ctx.guard;
await findUserById(userId);
const user = await updateUserById(userId, {
customData,
});
ctx.body = user.customData;
return next();
}
);
router.post(
'/password/verify',
koaGuard({
body: object({ password: string().regex(passwordRegEx) }),
}),
async (ctx, next) => {
const { id: userId } = ctx.auth;
const { password } = ctx.guard.body;
const user = await findUserById(userId);
await verifyUserPassword(user, password);
ctx.status = 204;
return next();
}
);
router.post(
'/password',
koaGuard({ body: object({ password: string().regex(passwordRegEx) }) }),
async (ctx, next) => {
const { id: userId } = ctx.auth;
const { password } = ctx.guard.body;
const user = await findUserById(userId);
assertThat(!user.isSuspended, new RequestError({ code: 'user.suspended', status: 401 }));
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
await updateUserById(userId, { passwordEncrypted, passwordEncryptionMethod });
ctx.status = 204;
return next();
}
);
}

View file

@ -0,0 +1,49 @@
import { VerificationCodeType } from '@logto/connector-kit';
import {
requestVerificationCodePayloadGuard,
verifyVerificationCodePayloadGuard,
} from '@logto/schemas';
import koaGuard from '#src/middleware/koa-guard.js';
import type { RouterInitArgs } from '#src/routes/types.js';
import type { AuthedMeRouter } from './types.js';
export default function verificationCodeRoutes<T extends AuthedMeRouter>(
...[router, tenant]: RouterInitArgs<T>
) {
const codeType = VerificationCodeType.Generic;
const {
passcodes: { createPasscode, sendPasscode, verifyPasscode },
} = tenant.libraries;
router.post(
'/verification-codes',
koaGuard({
body: requestVerificationCodePayloadGuard,
}),
async (ctx, next) => {
const code = await createPasscode(undefined, codeType, ctx.guard.body);
await sendPasscode(code);
ctx.status = 204;
return next();
}
);
router.post(
'/verification-codes/verify',
koaGuard({
body: verifyVerificationCodePayloadGuard,
}),
async (ctx, next) => {
const { verificationCode, ...identifier } = ctx.guard.body;
await verifyPasscode(undefined, codeType, verificationCode, identifier);
ctx.status = 204;
return next();
}
);
}