Initial commit
This commit is contained in:
20
packages/auth/src/integrations/api/procedures.ts
Normal file
20
packages/auth/src/integrations/api/procedures.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { t } from "@repo/core-shared/trpc/init";
|
||||
import { defineErrorMiddleware } from "@repo/core-shared/trpc/define-error-middleware";
|
||||
|
||||
import {
|
||||
AuthenticationError,
|
||||
UnauthenticatedError,
|
||||
UnauthorizedError,
|
||||
TooManyRequestsError,
|
||||
} from "../../entities/errors/auth";
|
||||
import { InputParseError } from "../../entities/errors/common";
|
||||
|
||||
export const authProcedure = t.procedure.use(
|
||||
defineErrorMiddleware([
|
||||
[InputParseError, "BAD_REQUEST"],
|
||||
[AuthenticationError, "UNAUTHORIZED"],
|
||||
[UnauthenticatedError, "UNAUTHORIZED"],
|
||||
[UnauthorizedError, "FORBIDDEN"],
|
||||
[TooManyRequestsError, "TOO_MANY_REQUESTS"],
|
||||
]),
|
||||
);
|
||||
74
packages/auth/src/integrations/api/router.test.ts
Normal file
74
packages/auth/src/integrations/api/router.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { describe, it, expect, beforeEach } from "vitest";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import { authRouter } from "@/integrations/api/router";
|
||||
import { authContainer } from "@/di/container";
|
||||
import { AUTH_SYMBOLS } from "@/di/symbols";
|
||||
import { MockUsersRepository } from "@/infrastructure/repositories/users.repository.mock";
|
||||
import { MockAuthenticationService } from "@/infrastructure/services/authentication.service.mock";
|
||||
import type { IUsersRepository } from "@/application/repositories/users.repository.interface";
|
||||
import type { IAuthenticationService } from "@/application/services/authentication.service.interface";
|
||||
|
||||
describe("authRouter", () => {
|
||||
it("exposes signIn, signUp, signOut procedures", () => {
|
||||
const names = Object.keys(authRouter._def.procedures);
|
||||
expect(names).toContain("signIn");
|
||||
expect(names).toContain("signUp");
|
||||
expect(names).toContain("signOut");
|
||||
});
|
||||
|
||||
it("signIn returns a cookie via container-resolved controller", async () => {
|
||||
// The router resolves controllers via the authContainer (default mock bindings).
|
||||
// MockUsersRepository is seeded with alice/password_alice by default.
|
||||
const caller = authRouter.createCaller({});
|
||||
const result = await caller.signIn({
|
||||
username: "alice",
|
||||
password: "password_alice",
|
||||
});
|
||||
expect(result.name).toBe("session");
|
||||
});
|
||||
});
|
||||
|
||||
describe("authRouter error mapping", () => {
|
||||
beforeEach(() => {
|
||||
if (authContainer.isBound(AUTH_SYMBOLS.IUsersRepository)) {
|
||||
authContainer.unbind(AUTH_SYMBOLS.IUsersRepository);
|
||||
}
|
||||
if (authContainer.isBound(AUTH_SYMBOLS.IAuthenticationService)) {
|
||||
authContainer.unbind(AUTH_SYMBOLS.IAuthenticationService);
|
||||
}
|
||||
const users = new MockUsersRepository();
|
||||
const auth = new MockAuthenticationService(users);
|
||||
authContainer
|
||||
.bind<IUsersRepository>(AUTH_SYMBOLS.IUsersRepository)
|
||||
.toConstantValue(users);
|
||||
authContainer
|
||||
.bind<IAuthenticationService>(AUTH_SYMBOLS.IAuthenticationService)
|
||||
.toConstantValue(auth);
|
||||
});
|
||||
|
||||
it("translates AuthenticationError → UNAUTHORIZED on missing user", async () => {
|
||||
const caller = authRouter.createCaller({});
|
||||
try {
|
||||
await caller.signIn({ username: "ghost", password: "long-enough" });
|
||||
throw new Error("expected throw");
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(TRPCError);
|
||||
expect((e as TRPCError).code).toBe("UNAUTHORIZED");
|
||||
}
|
||||
});
|
||||
|
||||
it("translates BAD_REQUEST when zod parse fails at the procedure boundary", async () => {
|
||||
const caller = authRouter.createCaller({});
|
||||
try {
|
||||
await caller.signIn({ username: "ab", password: "x" } as unknown as {
|
||||
username: string;
|
||||
password: string;
|
||||
});
|
||||
throw new Error("expected throw");
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(TRPCError);
|
||||
expect((e as TRPCError).code).toBe("BAD_REQUEST");
|
||||
}
|
||||
});
|
||||
});
|
||||
33
packages/auth/src/integrations/api/router.ts
Normal file
33
packages/auth/src/integrations/api/router.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { router } from "@repo/core-shared/trpc/init";
|
||||
|
||||
import { authContainer } from "../../di/container";
|
||||
import { AUTH_SYMBOLS } from "../../di/symbols";
|
||||
|
||||
import { signInInputSchema } from "../../application/use-cases/sign-in.use-case";
|
||||
import { signUpInputSchema } from "../../application/use-cases/sign-up.use-case";
|
||||
import { signOutInputSchema } from "../../application/use-cases/sign-out.use-case";
|
||||
|
||||
import type { ISignInController } from "../../interface-adapters/controllers/sign-in.controller";
|
||||
import type { ISignUpController } from "../../interface-adapters/controllers/sign-up.controller";
|
||||
import type { ISignOutController } from "../../interface-adapters/controllers/sign-out.controller";
|
||||
|
||||
import { authProcedure } from "./procedures";
|
||||
|
||||
export const authRouter = router({
|
||||
signIn: authProcedure.input(signInInputSchema).mutation(({ input }) => {
|
||||
const ctrl = authContainer.get<ISignInController>(AUTH_SYMBOLS.ISignInController);
|
||||
return ctrl(input);
|
||||
}),
|
||||
|
||||
signUp: authProcedure.input(signUpInputSchema).mutation(({ input }) => {
|
||||
const ctrl = authContainer.get<ISignUpController>(AUTH_SYMBOLS.ISignUpController);
|
||||
return ctrl(input);
|
||||
}),
|
||||
|
||||
signOut: authProcedure.input(signOutInputSchema).mutation(({ input }) => {
|
||||
const ctrl = authContainer.get<ISignOutController>(AUTH_SYMBOLS.ISignOutController);
|
||||
return ctrl(input);
|
||||
}),
|
||||
});
|
||||
|
||||
export type AuthRouter = typeof authRouter;
|
||||
49
packages/auth/src/integrations/cms/collections/users.ts
Normal file
49
packages/auth/src/integrations/cms/collections/users.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { CollectionConfig } from "payload";
|
||||
|
||||
export const users: CollectionConfig = {
|
||||
slug: "users",
|
||||
auth: true,
|
||||
admin: {
|
||||
useAsTitle: "email",
|
||||
},
|
||||
custom: {
|
||||
retention: {
|
||||
purgeSchedule: "daily",
|
||||
postDeletion: {
|
||||
duration: "P30D",
|
||||
trigger: "after-deletion",
|
||||
action: "hard-delete",
|
||||
},
|
||||
},
|
||||
subject: { kind: "self", field: "id" },
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: "displayName",
|
||||
type: "text",
|
||||
custom: {
|
||||
pii: {
|
||||
category: "identification-username",
|
||||
purpose: ["service-delivery"],
|
||||
exportable: true,
|
||||
restrictable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "role",
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: "Admin", value: "admin" },
|
||||
{ label: "Editor", value: "editor" },
|
||||
{ label: "Author", value: "author" },
|
||||
],
|
||||
defaultValue: "author",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: "consentState",
|
||||
type: "json",
|
||||
},
|
||||
],
|
||||
};
|
||||
2
packages/auth/src/integrations/cms/index.ts
Normal file
2
packages/auth/src/integrations/cms/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { users } from "./collections/users";
|
||||
// <gen:job-tasks>
|
||||
Reference in New Issue
Block a user