@goscribe/server 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/context.d.ts +2 -2
  2. package/dist/context.js +11 -7
  3. package/dist/index.d.ts +3 -4
  4. package/dist/index.js +1 -2
  5. package/dist/lib/auth.d.ts +3 -2
  6. package/dist/lib/auth.js +39 -36
  7. package/dist/lib/file.d.ts +0 -0
  8. package/dist/lib/file.js +1 -0
  9. package/dist/lib/prisma.d.ts +2 -3
  10. package/dist/lib/prisma.js +4 -7
  11. package/dist/lib/storage.d.ts +3 -0
  12. package/dist/lib/storage.js +10 -0
  13. package/dist/routers/_app.d.ts +90 -16
  14. package/dist/routers/_app.js +6 -9
  15. package/dist/routers/auth.d.ts +28 -2
  16. package/dist/routers/auth.js +48 -16
  17. package/dist/routers/workspace.d.ts +60 -14
  18. package/dist/routers/workspace.js +129 -30
  19. package/dist/server.d.ts +0 -1
  20. package/dist/server.js +19 -56
  21. package/dist/trpc.d.ts +10 -9
  22. package/dist/trpc.js +13 -26
  23. package/package.json +11 -4
  24. package/prisma/schema.prisma +7 -21
  25. package/src/context.ts +12 -4
  26. package/src/index.ts +3 -3
  27. package/src/lib/auth.ts +37 -32
  28. package/src/lib/prisma.ts +1 -1
  29. package/src/routers/_app.ts +3 -3
  30. package/src/routers/auth.ts +47 -2
  31. package/src/routers/workspace.ts +16 -15
  32. package/src/server.ts +2 -5
  33. package/src/trpc.ts +5 -11
  34. package/tsconfig.json +17 -10
  35. package/dist/context.d.ts.map +0 -1
  36. package/dist/index.d.ts.map +0 -1
  37. package/dist/lib/auth.d.ts.map +0 -1
  38. package/dist/lib/prisma.d.ts.map +0 -1
  39. package/dist/routers/_app.d.ts.map +0 -1
  40. package/dist/routers/auth.d.ts.map +0 -1
  41. package/dist/routers/sample.js +0 -21
  42. package/dist/routers/workspace.d.ts.map +0 -1
  43. package/dist/server.d.ts.map +0 -1
  44. package/dist/trpc.d.ts.map +0 -1
@@ -1,45 +1,144 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.workspace = void 0;
4
- const zod_1 = require("zod");
5
- const trpc_1 = require("../trpc");
6
- exports.workspace = (0, trpc_1.router)({
1
+ import { z } from 'zod';
2
+ import { router, authedProcedure } from '../trpc.js';
3
+ import { bucket } from '../lib/storage.js';
4
+ export const workspace = router({
7
5
  // Mutation with Zod input
8
- list: trpc_1.publicProcedure
6
+ list: authedProcedure
9
7
  .query(async ({ ctx, input }) => {
8
+ const workspaces = await ctx.db.workspace.findMany({
9
+ where: {
10
+ ownerId: ctx.session?.user.id,
11
+ },
12
+ });
13
+ return workspaces;
10
14
  }),
11
- create: trpc_1.publicProcedure
12
- .input(zod_1.z.object({}))
13
- .mutation(({ input }) => {
15
+ create: authedProcedure
16
+ .input(z.object({
17
+ name: z.string().min(1).max(100),
18
+ description: z.string().max(500).optional(),
19
+ }))
20
+ .mutation(({ ctx, input }) => {
21
+ return ctx.db.workspace.create({
22
+ data: {
23
+ title: input.name,
24
+ description: input.description,
25
+ ownerId: ctx.session?.user.id,
26
+ },
27
+ });
14
28
  }),
15
- get: trpc_1.publicProcedure
16
- .input(zod_1.z.object({
17
- id: zod_1.z.string().uuid(),
29
+ get: authedProcedure
30
+ .input(z.object({
31
+ id: z.string().uuid(),
18
32
  }))
19
- .query(({ input }) => {
33
+ .query(({ ctx, input }) => {
34
+ return ctx.db.workspace.findUnique({
35
+ where: {
36
+ id: input.id,
37
+ },
38
+ });
20
39
  }),
21
- update: trpc_1.publicProcedure
22
- .input(zod_1.z.object({
23
- id: zod_1.z.string().uuid(),
40
+ update: authedProcedure
41
+ .input(z.object({
42
+ id: z.string().uuid(),
43
+ name: z.string().min(1).max(100).optional(),
44
+ description: z.string().max(500).optional(),
24
45
  }))
25
- .mutation(({ input }) => {
46
+ .mutation(({ ctx, input }) => {
47
+ return ctx.db.workspace.update({
48
+ where: {
49
+ id: input.id,
50
+ },
51
+ data: {
52
+ title: input.name,
53
+ description: input.description,
54
+ },
55
+ });
26
56
  }),
27
- delete: trpc_1.publicProcedure
28
- .input(zod_1.z.object({
29
- id: zod_1.z.string().uuid(),
57
+ delete: authedProcedure
58
+ .input(z.object({
59
+ id: z.string().uuid(),
30
60
  }))
31
- .mutation(({ input }) => {
61
+ .mutation(({ ctx, input }) => {
62
+ ctx.db.workspace.delete({
63
+ where: {
64
+ id: input.id,
65
+ },
66
+ });
67
+ return true;
32
68
  }),
33
- upload: trpc_1.publicProcedure
34
- .input(zod_1.z.object({
35
- file: zod_1.z.string(),
69
+ uploadFiles: authedProcedure
70
+ .input(z.object({
71
+ id: z.string().uuid(),
72
+ files: z.array(z.object({
73
+ filename: z.string().min(1).max(255),
74
+ contentType: z.string().min(1).max(100),
75
+ size: z.number().min(1), // size in bytes
76
+ })),
36
77
  }))
37
- .mutation(({ input }) => {
78
+ .mutation(async ({ ctx, input }) => {
79
+ const results = [];
80
+ for (const file of input.files) {
81
+ // 1. Insert into DB
82
+ const record = await ctx.db.fileAsset.create({
83
+ data: {
84
+ userId: ctx.session.user.id,
85
+ name: file.filename,
86
+ mimeType: file.contentType,
87
+ size: file.size,
88
+ workspaceId: input.id,
89
+ },
90
+ });
91
+ // 2. Generate signed URL for direct upload
92
+ const [url] = await bucket
93
+ .file(`${ctx.session.user.id}/${record.id}-${file.filename}`)
94
+ .getSignedUrl({
95
+ action: "write",
96
+ expires: Date.now() + 5 * 60 * 1000, // 5 min
97
+ contentType: file.contentType,
98
+ });
99
+ // 3. Update record with bucket info
100
+ await ctx.db.fileAsset.update({
101
+ where: { id: record.id },
102
+ data: {
103
+ bucket: bucket.name,
104
+ objectKey: `${ctx.session.user.id}/${record.id}-${file.filename}`,
105
+ },
106
+ });
107
+ results.push({
108
+ fileId: record.id,
109
+ uploadUrl: url,
110
+ });
111
+ }
112
+ return results;
38
113
  }),
39
- deleteFile: trpc_1.publicProcedure
40
- .input(zod_1.z.object({
41
- fileId: zod_1.z.string().uuid(),
114
+ deleteFiles: authedProcedure
115
+ .input(z.object({
116
+ fileId: z.array(z.string().uuid()),
117
+ id: z.string().uuid(),
42
118
  }))
43
- .mutation(({ input }) => {
119
+ .mutation(({ ctx, input }) => {
120
+ const files = ctx.db.fileAsset.findMany({
121
+ where: {
122
+ id: { in: input.fileId },
123
+ workspaceId: input.id,
124
+ },
125
+ });
126
+ // Delete from GCS
127
+ files.then((fileRecords) => {
128
+ fileRecords.forEach((file) => {
129
+ if (file.bucket && file.objectKey) {
130
+ const gcsFile = bucket.file(file.objectKey);
131
+ gcsFile.delete({ ignoreNotFound: true }).catch((err) => {
132
+ console.error(`Error deleting file ${file.objectKey} from bucket ${file.bucket}:`, err);
133
+ });
134
+ }
135
+ });
136
+ });
137
+ return ctx.db.fileAsset.deleteMany({
138
+ where: {
139
+ id: { in: input.fileId },
140
+ workspaceId: input.id,
141
+ },
142
+ });
44
143
  }),
45
144
  });
package/dist/server.d.ts CHANGED
@@ -1,2 +1 @@
1
1
  export {};
2
- //# sourceMappingURL=server.d.ts.map
package/dist/server.js CHANGED
@@ -1,68 +1,31 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- const express_1 = __importDefault(require("express"));
40
- const cors_1 = __importDefault(require("cors"));
41
- const helmet_1 = __importDefault(require("helmet"));
42
- const morgan_1 = __importDefault(require("morgan"));
43
- const compression_1 = __importDefault(require("compression"));
44
- const trpcExpress = __importStar(require("@trpc/server/adapters/express"));
45
- const auth_1 = require("./lib/auth");
46
- const _app_1 = require("./routers/_app");
47
- const context_1 = require("./context");
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import helmet from 'helmet';
4
+ import morgan from 'morgan';
5
+ import compression from 'compression';
6
+ import * as trpcExpress from '@trpc/server/adapters/express';
7
+ import { appRouter } from './routers/_app.js';
8
+ import { createContext } from './context.js';
48
9
  const PORT = process.env.PORT ? Number(process.env.PORT) : 3001;
49
10
  async function main() {
50
- const app = (0, express_1.default)();
11
+ const app = express();
51
12
  // Middlewares
52
- app.use((0, helmet_1.default)());
53
- app.use((0, cors_1.default)({ origin: true, credentials: true }));
54
- app.use((0, morgan_1.default)('dev'));
55
- app.use((0, compression_1.default)());
56
- app.use(express_1.default.json());
57
- app.use("/auth", auth_1.authRouter); // Auth routes live under /auth/*
13
+ app.use(helmet());
14
+ app.use(cors({
15
+ origin: "http://localhost:3000", // your Next.js dev URL
16
+ credentials: true, // allow cookies
17
+ }));
18
+ app.use(morgan('dev'));
19
+ app.use(compression());
20
+ app.use(express.json());
58
21
  // Health (plain Express)
59
22
  app.get('/', (_req, res) => {
60
23
  res.json({ ok: true, service: 'trpc-express', ts: Date.now() });
61
24
  });
62
25
  // tRPC mounted under /trpc
63
26
  app.use('/trpc', trpcExpress.createExpressMiddleware({
64
- router: _app_1.appRouter,
65
- createContext: context_1.createContext,
27
+ router: appRouter,
28
+ createContext,
66
29
  }));
67
30
  app.listen(PORT, () => {
68
31
  console.log(`✅ Server ready on http://localhost:${PORT}`);
package/dist/trpc.d.ts CHANGED
@@ -1,41 +1,42 @@
1
1
  export declare const router: import("@trpc/server").TRPCRouterBuilder<{
2
2
  ctx: {
3
- db: import("src/generated/prisma").PrismaClient<import("src/generated/prisma").Prisma.PrismaClientOptions, never, import("src/generated/prisma/runtime/library").DefaultArgs>;
3
+ db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
4
4
  session: any;
5
5
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
6
6
  res: import("express").Response<any, Record<string, any>>;
7
+ cookies: Record<string, string | undefined>;
7
8
  };
8
9
  meta: object;
9
10
  errorShape: import("@trpc/server").TRPCDefaultErrorShape;
10
11
  transformer: true;
11
12
  }>;
12
13
  export declare const middleware: <$ContextOverrides>(fn: import("@trpc/server").TRPCMiddlewareFunction<{
13
- db: import("src/generated/prisma").PrismaClient<import("src/generated/prisma").Prisma.PrismaClientOptions, never, import("src/generated/prisma/runtime/library").DefaultArgs>;
14
+ db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
14
15
  session: any;
15
16
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
16
17
  res: import("express").Response<any, Record<string, any>>;
18
+ cookies: Record<string, string | undefined>;
17
19
  }, object, object, $ContextOverrides, unknown>) => import("@trpc/server").TRPCMiddlewareBuilder<{
18
- db: import("src/generated/prisma").PrismaClient<import("src/generated/prisma").Prisma.PrismaClientOptions, never, import("src/generated/prisma/runtime/library").DefaultArgs>;
20
+ db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
19
21
  session: any;
20
22
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
21
23
  res: import("express").Response<any, Record<string, any>>;
24
+ cookies: Record<string, string | undefined>;
22
25
  }, object, $ContextOverrides, unknown>;
23
26
  export declare const publicProcedure: import("@trpc/server").TRPCProcedureBuilder<{
24
- db: import("src/generated/prisma").PrismaClient<import("src/generated/prisma").Prisma.PrismaClientOptions, never, import("src/generated/prisma/runtime/library").DefaultArgs>;
27
+ db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
25
28
  session: any;
26
29
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
27
30
  res: import("express").Response<any, Record<string, any>>;
31
+ cookies: Record<string, string | undefined>;
28
32
  }, object, object, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
29
33
  /** Exported authed procedure */
30
34
  export declare const authedProcedure: import("@trpc/server").TRPCProcedureBuilder<{
31
- db: import("src/generated/prisma").PrismaClient<import("src/generated/prisma").Prisma.PrismaClientOptions, never, import("src/generated/prisma/runtime/library").DefaultArgs>;
35
+ db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
32
36
  session: any;
33
37
  req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
34
38
  res: import("express").Response<any, Record<string, any>>;
39
+ cookies: Record<string, string | undefined>;
35
40
  }, object, {
36
- req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
37
- res: import("express").Response<any, Record<string, any>>;
38
- db: import("src/generated/prisma").PrismaClient<import("src/generated/prisma").Prisma.PrismaClientOptions, never, import("src/generated/prisma/runtime/library").DefaultArgs>;
39
41
  session: any;
40
42
  }, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
41
- //# sourceMappingURL=trpc.d.ts.map
package/dist/trpc.js CHANGED
@@ -1,38 +1,25 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.authedProcedure = exports.publicProcedure = exports.middleware = exports.router = void 0;
7
- const server_1 = require("@trpc/server");
8
- const superjson_1 = __importDefault(require("superjson"));
9
- const t = server_1.initTRPC.context().create({
10
- transformer: superjson_1.default,
1
+ import { initTRPC, TRPCError } from "@trpc/server";
2
+ import superjson from "superjson";
3
+ const t = initTRPC.context().create({
4
+ transformer: superjson,
11
5
  errorFormatter({ shape }) {
12
6
  return shape;
13
7
  },
14
8
  });
15
- exports.router = t.router;
16
- exports.middleware = t.middleware;
17
- exports.publicProcedure = t.procedure;
9
+ export const router = t.router;
10
+ export const middleware = t.middleware;
11
+ export const publicProcedure = t.procedure;
18
12
  /** Middleware that enforces authentication */
19
- const isAuthed = (0, exports.middleware)(({ ctx, next }) => {
20
- if (!ctx.session?.user?.id) {
21
- throw new server_1.TRPCError({ code: "UNAUTHORIZED" });
13
+ const isAuthed = middleware(({ ctx, next }) => {
14
+ const hasUser = Boolean(ctx.session?.user?.id);
15
+ if (!ctx.session || !hasUser) {
16
+ throw new TRPCError({ code: "UNAUTHORIZED" });
22
17
  }
23
18
  return next({
24
19
  ctx: {
25
- ...ctx,
26
- // refine ctx: session is guaranteed, user.id is string
27
- session: {
28
- ...ctx.session,
29
- user: {
30
- ...ctx.session.user,
31
- id: ctx.session.user.id, // typed non-null
32
- },
33
- },
20
+ session: ctx.session,
34
21
  },
35
22
  });
36
23
  });
37
24
  /** Exported authed procedure */
38
- exports.authedProcedure = exports.publicProcedure.use(isAuthed);
25
+ export const authedProcedure = publicProcedure.use(isAuthed);
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@goscribe/server",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "type": "module",
7
8
  "exports": {
8
9
  ".": {
9
10
  "types": "./dist/index.d.ts",
@@ -12,18 +13,21 @@
12
13
  },
13
14
  "scripts": {
14
15
  "dev": "ts-node-dev --respawn --transpile-only src/server.ts",
15
- "build": "tsc -p .",
16
- "start": "node dist/server.js"
16
+ "build": "npx prisma generate && tsc -p .",
17
+ "start": "node --experimental-specifier-resolution=node dist/server.js"
17
18
  },
18
19
  "author": "",
19
20
  "license": "MIT",
20
21
  "dependencies": {
21
22
  "@auth/express": "^0.11.0",
22
23
  "@auth/prisma-adapter": "^2.10.0",
24
+ "@google-cloud/storage": "^7.17.0",
23
25
  "@prisma/client": "^6.14.0",
24
26
  "@trpc/server": "^11.5.0",
27
+ "@types/cookie": "^1.0.0",
25
28
  "bcryptjs": "^3.0.2",
26
29
  "compression": "^1.8.1",
30
+ "cookie": "^1.0.2",
27
31
  "cors": "^2.8.5",
28
32
  "express": "^5.1.0",
29
33
  "helmet": "^8.1.0",
@@ -40,6 +44,9 @@
40
44
  "@types/node": "^24.3.0",
41
45
  "ts-node": "^10.9.2",
42
46
  "ts-node-dev": "^2.0.0",
43
- "typescript": "^5.9.2"
47
+ "tsc-alias": "^1.8.16",
48
+ "tsc-esm-fix": "^3.1.2",
49
+ "typescript": "^5.9.2",
50
+ "typescript-transform-paths": "^3.5.5"
44
51
  }
45
52
  }
@@ -1,12 +1,12 @@
1
1
 
2
2
  generator client {
3
3
  provider = "prisma-client-js"
4
- output = "../src/generated/prisma"
5
4
  }
6
5
 
7
6
  datasource db {
8
7
  provider = "postgresql"
9
8
  url = env("DATABASE_URL")
9
+ directUrl = env("DIRECT_URL") // for shadow db in migrations
10
10
  }
11
11
 
12
12
  //
@@ -36,6 +36,7 @@ model User {
36
36
  emailVerified DateTime?
37
37
  passwordHash String? // for credentials login
38
38
  image String?
39
+ session Session[]
39
40
 
40
41
  // Ownership
41
42
  folders Folder[] @relation("UserFolders")
@@ -44,30 +45,15 @@ model User {
44
45
  artifacts Artifact[] @relation("UserArtifacts")
45
46
  versions ArtifactVersion[] @relation("UserArtifactVersions")
46
47
 
47
- accounts Account[]
48
-
49
48
  createdAt DateTime @default(now())
50
49
  updatedAt DateTime @updatedAt
51
50
  }
52
51
 
53
- model Account {
54
- id String @id @default(cuid())
55
- userId String
56
- type String
57
- provider String
58
- providerAccountId String
59
-
60
- refresh_token String?
61
- access_token String?
62
- expires_at Int?
63
- token_type String?
64
- scope String?
65
- id_token String?
66
- session_state String?
67
-
68
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
69
-
70
- @@unique([provider, providerAccountId])
52
+ model Session {
53
+ id String @id @default(cuid())
54
+ userId String
55
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
56
+ expires DateTime
71
57
  }
72
58
 
73
59
  model VerificationToken {
package/src/context.ts CHANGED
@@ -1,11 +1,19 @@
1
1
  // src/server/trpc/context.ts
2
2
  import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
3
- import { prisma } from "./lib/prisma";
3
+ import { prisma } from "./lib/prisma.js";
4
+ import { verifyCustomAuthCookie } from "./lib/auth.js";
5
+ import cookie from "cookie";
4
6
 
5
7
  export async function createContext({ req, res }: CreateExpressContextOptions) {
6
- const session = (req as any).auth ?? null;
7
-
8
- return { db: prisma, session, req, res };
8
+ const cookies = cookie.parse(req.headers.cookie ?? "");
9
+
10
+ // Only use custom auth cookie
11
+ const custom = verifyCustomAuthCookie(cookies["auth_token"]);
12
+ if (custom) {
13
+ return { db: prisma, session: { user: { id: custom.userId } } as any, req, res, cookies };
14
+ }
15
+
16
+ return { db: prisma, session: null, req, res, cookies };
9
17
  }
10
18
 
11
19
  export type Context = Awaited<ReturnType<typeof createContext>>;
package/src/index.ts CHANGED
@@ -1,3 +1,3 @@
1
- export type { AppRouter } from "./routers/_app";
2
- export type { RouterInputs } from "./routers/_app";
3
- export type { RouterOutputs } from "./routers/_app";
1
+ export type { AppRouter } from "./routers/_app.js";
2
+ export type { RouterInputs } from "./routers/_app.js";
3
+ export type { RouterOutputs } from "./routers/_app.js";
package/src/lib/auth.ts CHANGED
@@ -1,34 +1,39 @@
1
1
  // src/server/auth.ts
2
- import { ExpressAuth } from "@auth/express";
3
- import { PrismaAdapter } from "@auth/prisma-adapter";
4
- import { prisma } from "../lib/prisma";
5
- import Google from "@auth/core/providers/google";
6
- import Credentials from "@auth/core/providers/credentials";
2
+ import crypto from "node:crypto";
7
3
 
8
- export const authRouter = ExpressAuth({
9
- providers: [
10
- Google({
11
- clientId: process.env.GOOGLE_CLIENT_ID!,
12
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
13
- }),
14
- Credentials({
15
- name: "credentials",
16
- credentials: {
17
- email: { label: "Email", type: "email" },
18
- password: { label: "Password", type: "password" },
19
- },
20
- async authorize(credentials) {
21
- if (
22
- credentials?.email === "demo@example.com" &&
23
- credentials?.password === "demo"
24
- ) {
25
- return { id: "1", email: "demo@example.com", name: "Demo User" };
26
- }
27
- return null;
28
- },
29
- }),
30
- ],
31
- adapter: PrismaAdapter(prisma),
32
- secret: process.env.AUTH_SECRET!,
33
- session: { strategy: "jwt" },
34
- })
4
+ // Custom HMAC cookie: auth_token = base64(userId).hex(hmacSHA256(base64(userId), secret))
5
+ export function verifyCustomAuthCookie(cookieValue: string | undefined): { userId: string } | null {
6
+ if (!cookieValue) return null;
7
+ const secret = process.env.CUSTOM_AUTH_SECRET;
8
+ if (!secret) return null;
9
+
10
+ const parts = cookieValue.split(".");
11
+ if (parts.length !== 2) return null;
12
+ const [base64UserId, signatureHex] = parts;
13
+
14
+ let userId: string;
15
+ try {
16
+ const buf = Buffer.from(base64UserId, "base64url");
17
+ userId = buf.toString("utf8");
18
+ } catch {
19
+ return null;
20
+ }
21
+
22
+ const hmac = crypto.createHmac("sha256", secret);
23
+ hmac.update(base64UserId);
24
+ const expected = hmac.digest("hex");
25
+ if (!timingSafeEqualHex(signatureHex, expected)) return null;
26
+
27
+ return { userId };
28
+ }
29
+
30
+ function timingSafeEqualHex(a: string, b: string): boolean {
31
+ try {
32
+ const ab = Buffer.from(a, "hex");
33
+ const bb = Buffer.from(b, "hex");
34
+ if (ab.length !== bb.length) return false;
35
+ return crypto.timingSafeEqual(ab, bb);
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
package/src/lib/prisma.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { PrismaClient } from "../generated/prisma";
1
+ import { PrismaClient } from "@prisma/client";
2
2
 
3
3
  const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
4
4
 
@@ -1,7 +1,7 @@
1
1
  import { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
2
- import { router } from '../trpc';
3
- import { auth } from './auth';
4
- import { workspace } from './workspace';
2
+ import { router } from '../trpc.js';
3
+ import { auth } from './auth.js';
4
+ import { workspace } from './workspace.js';
5
5
 
6
6
  export const appRouter = router({
7
7
  auth,