2023-09-27 17:56:32 +08:00
|
|
|
import { initTRPC, inferAsyncReturnType, TRPCError } from '@trpc/server';
|
2023-09-27 20:27:46 +08:00
|
|
|
import _ from 'lodash';
|
|
|
|
import { z } from 'zod';
|
2024-07-28 20:32:41 +08:00
|
|
|
import { jwtVerify } from '../middleware/auth.js';
|
|
|
|
import { getWorkspaceUser } from '../model/workspace.js';
|
2024-01-24 13:26:42 +00:00
|
|
|
import { ROLES, SYSTEM_ROLES } from '@tianji/shared';
|
2024-07-31 00:40:04 +08:00
|
|
|
import type { Request } from 'express';
|
2023-10-22 00:26:13 +08:00
|
|
|
import { OpenApiMeta } from 'trpc-openapi';
|
2024-07-31 00:40:04 +08:00
|
|
|
import { getSession } from '@auth/express';
|
|
|
|
import { authConfig } from '../model/auth.js';
|
2023-09-27 17:56:32 +08:00
|
|
|
|
2024-07-31 00:40:04 +08:00
|
|
|
export async function createContext({ req }: { req: Request }) {
|
2023-09-27 17:56:32 +08:00
|
|
|
const authorization = req.headers['authorization'] ?? '';
|
|
|
|
const token = authorization.replace('Bearer ', '');
|
|
|
|
|
2024-04-28 14:55:29 +08:00
|
|
|
return { token, req };
|
2023-09-27 17:56:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
type Context = inferAsyncReturnType<typeof createContext>;
|
2023-10-22 00:26:13 +08:00
|
|
|
const t = initTRPC.context<Context>().meta<OpenApiMeta>().create();
|
2023-09-27 17:56:32 +08:00
|
|
|
|
2023-10-23 01:44:08 +08:00
|
|
|
export type OpenApiMetaInfo = NonNullable<OpenApiMeta['openapi']>;
|
|
|
|
|
2023-09-27 17:56:32 +08:00
|
|
|
export const middleware = t.middleware;
|
|
|
|
export const router = t.router;
|
|
|
|
export const publicProcedure = t.procedure;
|
|
|
|
|
2023-10-03 15:56:09 +08:00
|
|
|
const isUser = middleware(async (opts) => {
|
2024-07-31 00:40:04 +08:00
|
|
|
// auth with token
|
2023-10-03 15:56:09 +08:00
|
|
|
const token = opts.ctx.token;
|
|
|
|
|
2024-07-31 00:40:04 +08:00
|
|
|
if (token) {
|
|
|
|
try {
|
|
|
|
const user = jwtVerify(token);
|
|
|
|
|
|
|
|
return opts.next({
|
|
|
|
ctx: {
|
|
|
|
user,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
} catch (err) {
|
|
|
|
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'TokenInvalid' });
|
|
|
|
}
|
2023-10-03 15:56:09 +08:00
|
|
|
}
|
|
|
|
|
2024-07-31 00:40:04 +08:00
|
|
|
// auth with session
|
|
|
|
const req = opts.ctx.req;
|
|
|
|
const session = await getSession(req, authConfig);
|
2023-10-03 15:56:09 +08:00
|
|
|
|
2024-07-31 00:40:04 +08:00
|
|
|
if (session) {
|
2023-10-03 15:56:09 +08:00
|
|
|
return opts.next({
|
|
|
|
ctx: {
|
2024-07-31 00:40:04 +08:00
|
|
|
user: {
|
|
|
|
id: session.user?.id,
|
|
|
|
username: session.user?.name,
|
|
|
|
role: SYSTEM_ROLES.user,
|
|
|
|
},
|
2023-10-03 15:56:09 +08:00
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2024-07-31 00:40:04 +08:00
|
|
|
|
|
|
|
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'No Token or Session' });
|
2023-10-03 15:56:09 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
export const protectProedure = t.procedure.use(isUser);
|
|
|
|
|
|
|
|
const isSystemAdmin = isUser.unstable_pipe(async (opts) => {
|
2023-09-27 17:56:32 +08:00
|
|
|
const { ctx, input } = opts;
|
2023-10-03 15:56:09 +08:00
|
|
|
|
2023-09-27 17:56:32 +08:00
|
|
|
if (ctx.user.role !== SYSTEM_ROLES.admin) {
|
|
|
|
throw new TRPCError({ code: 'FORBIDDEN' });
|
|
|
|
}
|
|
|
|
|
|
|
|
return opts.next();
|
|
|
|
});
|
|
|
|
|
|
|
|
export const systemAdminProcedure = t.procedure.use(isSystemAdmin);
|
2023-10-03 15:56:09 +08:00
|
|
|
export const workspaceProcedure = protectProedure
|
2023-09-27 20:27:46 +08:00
|
|
|
.input(
|
|
|
|
z.object({
|
2023-10-06 00:06:44 +08:00
|
|
|
workspaceId: z.string().cuid2(),
|
2023-09-27 20:27:46 +08:00
|
|
|
})
|
|
|
|
)
|
|
|
|
.use(createWorkspacePermissionMiddleware());
|
2023-10-03 15:56:09 +08:00
|
|
|
export const workspaceOwnerProcedure = protectProedure
|
2023-09-27 20:27:46 +08:00
|
|
|
.input(
|
|
|
|
z.object({
|
2023-10-06 00:06:44 +08:00
|
|
|
workspaceId: z.string().cuid2(),
|
2023-09-27 20:27:46 +08:00
|
|
|
})
|
|
|
|
)
|
|
|
|
.use(createWorkspacePermissionMiddleware([ROLES.owner]));
|
2023-09-27 17:56:32 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a trpc middleware which help user check workspace permission
|
|
|
|
*/
|
|
|
|
function createWorkspacePermissionMiddleware(roles: ROLES[] = []) {
|
2023-10-03 15:56:09 +08:00
|
|
|
return isUser.unstable_pipe(async (opts) => {
|
2023-09-27 17:56:32 +08:00
|
|
|
const { ctx, input } = opts;
|
|
|
|
|
|
|
|
const workspaceId = _.get(input, 'workspaceId', '');
|
|
|
|
if (!workspaceId) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: 'INTERNAL_SERVER_ERROR',
|
|
|
|
message: 'Payload required workspaceId',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const userId = ctx.user.id;
|
|
|
|
|
|
|
|
if (!userId) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: 'INTERNAL_SERVER_ERROR',
|
|
|
|
message: 'ctx miss userId',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const info = await getWorkspaceUser(workspaceId, userId);
|
|
|
|
if (!info) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: 'FORBIDDEN',
|
|
|
|
message: 'Is not workspace user',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Array.isArray(roles) && roles.length > 0) {
|
|
|
|
if (!roles.includes(info.role as ROLES)) {
|
|
|
|
throw new TRPCError({
|
|
|
|
code: 'FORBIDDEN',
|
|
|
|
message: `Workspace roles not has this permission, need ${roles}`,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return opts.next();
|
|
|
|
});
|
|
|
|
}
|