mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-02-04 08:06:56 +00:00
tRPC Authorization 미들웨어 구현
This commit is contained in:
@@ -4,7 +4,7 @@ import { authenticate, AuthenticationError } from "$lib/server/modules/auth";
|
|||||||
|
|
||||||
export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
|
export const authenticateMiddleware: Handle = async ({ event, resolve }) => {
|
||||||
const { pathname, search } = event.url;
|
const { pathname, search } = event.url;
|
||||||
if (pathname === "/api/auth/login") {
|
if (pathname === "/api/auth/login" || pathname.startsWith("/trpc")) {
|
||||||
return await resolve(event);
|
return await resolve(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,17 @@ interface Session {
|
|||||||
clientId?: number;
|
clientId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ClientSession extends Session {
|
export interface ClientSession extends Session {
|
||||||
clientId: number;
|
clientId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SessionPermission =
|
||||||
|
| "any"
|
||||||
|
| "notClient"
|
||||||
|
| "anyClient"
|
||||||
|
| "pendingClient"
|
||||||
|
| "activeClient";
|
||||||
|
|
||||||
export class AuthenticationError extends Error {
|
export class AuthenticationError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
public status: 400 | 401,
|
public status: 400 | 401,
|
||||||
@@ -25,6 +32,16 @@ export class AuthenticationError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AuthorizationError extends Error {
|
||||||
|
constructor(
|
||||||
|
public status: 403 | 500,
|
||||||
|
message: string,
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.name = "AuthorizationError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const startSession = async (userId: number, ip: string, userAgent: string) => {
|
export const startSession = async (userId: number, ip: string, userAgent: string) => {
|
||||||
const { sessionId, sessionIdSigned } = await issueSessionId(32, env.session.secret);
|
const { sessionId, sessionIdSigned } = await issueSessionId(32, env.session.secret);
|
||||||
await createSession(userId, sessionId, ip, userAgent);
|
await createSession(userId, sessionId, ip, userAgent);
|
||||||
@@ -52,34 +69,12 @@ export const authenticate = async (sessionIdSigned: string, ip: string, userAgen
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function authorize(locals: App.Locals, requiredPermission: "any"): Promise<Session>;
|
export const authorizeInternal = async (
|
||||||
|
|
||||||
export async function authorize(
|
|
||||||
locals: App.Locals,
|
locals: App.Locals,
|
||||||
requiredPermission: "notClient",
|
requiredPermission: SessionPermission,
|
||||||
): Promise<Session>;
|
): Promise<Session> => {
|
||||||
|
|
||||||
export async function authorize(
|
|
||||||
locals: App.Locals,
|
|
||||||
requiredPermission: "anyClient",
|
|
||||||
): Promise<ClientSession>;
|
|
||||||
|
|
||||||
export async function authorize(
|
|
||||||
locals: App.Locals,
|
|
||||||
requiredPermission: "pendingClient",
|
|
||||||
): Promise<ClientSession>;
|
|
||||||
|
|
||||||
export async function authorize(
|
|
||||||
locals: App.Locals,
|
|
||||||
requiredPermission: "activeClient",
|
|
||||||
): Promise<ClientSession>;
|
|
||||||
|
|
||||||
export async function authorize(
|
|
||||||
locals: App.Locals,
|
|
||||||
requiredPermission: "any" | "notClient" | "anyClient" | "pendingClient" | "activeClient",
|
|
||||||
): Promise<Session> {
|
|
||||||
if (!locals.session) {
|
if (!locals.session) {
|
||||||
error(500, "Unauthenticated");
|
throw new AuthorizationError(500, "Unauthenticated");
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id: sessionId, userId, clientId } = locals.session;
|
const { id: sessionId, userId, clientId } = locals.session;
|
||||||
@@ -89,39 +84,63 @@ export async function authorize(
|
|||||||
break;
|
break;
|
||||||
case "notClient":
|
case "notClient":
|
||||||
if (clientId) {
|
if (clientId) {
|
||||||
error(403, "Forbidden");
|
throw new AuthorizationError(403, "Forbidden");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "anyClient":
|
case "anyClient":
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
error(403, "Forbidden");
|
throw new AuthorizationError(403, "Forbidden");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "pendingClient": {
|
case "pendingClient": {
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
error(403, "Forbidden");
|
throw new AuthorizationError(403, "Forbidden");
|
||||||
}
|
}
|
||||||
const userClient = await getUserClient(userId, clientId);
|
const userClient = await getUserClient(userId, clientId);
|
||||||
if (!userClient) {
|
if (!userClient) {
|
||||||
error(500, "Invalid session id");
|
throw new AuthorizationError(500, "Invalid session id");
|
||||||
} else if (userClient.state !== "pending") {
|
} else if (userClient.state !== "pending") {
|
||||||
error(403, "Forbidden");
|
throw new AuthorizationError(403, "Forbidden");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "activeClient": {
|
case "activeClient": {
|
||||||
if (!clientId) {
|
if (!clientId) {
|
||||||
error(403, "Forbidden");
|
throw new AuthorizationError(403, "Forbidden");
|
||||||
}
|
}
|
||||||
const userClient = await getUserClient(userId, clientId);
|
const userClient = await getUserClient(userId, clientId);
|
||||||
if (!userClient) {
|
if (!userClient) {
|
||||||
error(500, "Invalid session id");
|
throw new AuthorizationError(500, "Invalid session id");
|
||||||
} else if (userClient.state !== "active") {
|
} else if (userClient.state !== "active") {
|
||||||
error(403, "Forbidden");
|
throw new AuthorizationError(403, "Forbidden");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { sessionId, userId, clientId };
|
return { sessionId, userId, clientId };
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function authorize(
|
||||||
|
locals: App.Locals,
|
||||||
|
requiredPermission: "any" | "notClient",
|
||||||
|
): Promise<Session>;
|
||||||
|
|
||||||
|
export async function authorize(
|
||||||
|
locals: App.Locals,
|
||||||
|
requiredPermission: "anyClient" | "pendingClient" | "activeClient",
|
||||||
|
): Promise<ClientSession>;
|
||||||
|
|
||||||
|
export async function authorize(
|
||||||
|
locals: App.Locals,
|
||||||
|
requiredPermission: SessionPermission,
|
||||||
|
): Promise<Session> {
|
||||||
|
try {
|
||||||
|
return await authorizeInternal(locals, requiredPermission);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof AuthorizationError) {
|
||||||
|
error(e.status, e.message);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,25 @@
|
|||||||
import type { RequestEvent } from "@sveltejs/kit";
|
import type { RequestEvent } from "@sveltejs/kit";
|
||||||
import { initTRPC } from "@trpc/server";
|
import { initTRPC, TRPCError } from "@trpc/server";
|
||||||
|
import { authorizeMiddleware, authorizeClientMiddleware } from "./middlewares/authorize";
|
||||||
|
|
||||||
export const createContext = (event: RequestEvent) => event;
|
export const createContext = (event: RequestEvent) => event;
|
||||||
|
|
||||||
const t = initTRPC.context<Awaited<ReturnType<typeof createContext>>>().create();
|
export const t = initTRPC.context<Awaited<ReturnType<typeof createContext>>>().create();
|
||||||
|
|
||||||
export const router = t.router;
|
export const router = t.router;
|
||||||
export const publicProcedure = t.procedure;
|
export const publicProcedure = t.procedure;
|
||||||
|
|
||||||
|
const authedProcedure = publicProcedure.use(async ({ ctx, next }) => {
|
||||||
|
if (!ctx.locals.session) {
|
||||||
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
}
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
|
||||||
|
export const roleProcedure = {
|
||||||
|
any: authedProcedure.use(authorizeMiddleware("any")),
|
||||||
|
notClient: authedProcedure.use(authorizeMiddleware("notClient")),
|
||||||
|
anyClient: authedProcedure.use(authorizeClientMiddleware("anyClient")),
|
||||||
|
pendingClient: authedProcedure.use(authorizeClientMiddleware("pendingClient")),
|
||||||
|
activeClient: authedProcedure.use(authorizeClientMiddleware("activeClient")),
|
||||||
|
};
|
||||||
|
|||||||
36
src/trpc/middlewares/authorize.ts
Normal file
36
src/trpc/middlewares/authorize.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import {
|
||||||
|
AuthorizationError,
|
||||||
|
authorizeInternal,
|
||||||
|
type ClientSession,
|
||||||
|
type SessionPermission,
|
||||||
|
} from "$lib/server/modules/auth";
|
||||||
|
import { t } from "../init.server";
|
||||||
|
|
||||||
|
const authorize = async (locals: App.Locals, requiredPermission: SessionPermission) => {
|
||||||
|
try {
|
||||||
|
return await authorizeInternal(locals, requiredPermission);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof AuthorizationError) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: e.status === 403 ? "FORBIDDEN" : "INTERNAL_SERVER_ERROR",
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authorizeMiddleware = (requiredPermission: "any" | "notClient") =>
|
||||||
|
t.middleware(async ({ ctx, next }) => {
|
||||||
|
const session = await authorize(ctx.locals, requiredPermission);
|
||||||
|
return next({ ctx: { session } });
|
||||||
|
});
|
||||||
|
|
||||||
|
export const authorizeClientMiddleware = (
|
||||||
|
requiredPermission: "anyClient" | "pendingClient" | "activeClient",
|
||||||
|
) =>
|
||||||
|
t.middleware(async ({ ctx, next }) => {
|
||||||
|
const session = (await authorize(ctx.locals, requiredPermission)) as ClientSession;
|
||||||
|
return next({ ctx: { session } });
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user