@goscribe/server 1.0.3 → 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.
- package/dist/context.d.ts +2 -2
- package/dist/context.js +11 -7
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -2
- package/dist/lib/auth.d.ts +3 -2
- package/dist/lib/auth.js +39 -36
- package/dist/lib/file.d.ts +0 -1
- package/dist/lib/prisma.d.ts +1 -2
- package/dist/lib/prisma.js +4 -7
- package/dist/lib/storage.d.ts +0 -1
- package/dist/lib/storage.js +3 -6
- package/dist/routers/_app.d.ts +33 -5
- package/dist/routers/_app.js +6 -9
- package/dist/routers/auth.d.ts +28 -2
- package/dist/routers/auth.js +48 -16
- package/dist/routers/workspace.d.ts +3 -3
- package/dist/routers/workspace.js +34 -37
- package/dist/server.d.ts +0 -1
- package/dist/server.js +16 -56
- package/dist/trpc.d.ts +10 -9
- package/dist/trpc.js +13 -26
- package/package.json +3 -1
- package/prisma/schema.prisma +7 -20
- package/src/context.ts +11 -3
- package/src/lib/auth.ts +37 -32
- package/src/routers/auth.ts +46 -1
- package/src/routers/workspace.ts +7 -7
- package/src/server.ts +0 -3
- package/src/trpc.ts +4 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/auth.d.ts.map +0 -1
- package/dist/lib/file.d.ts.map +0 -1
- package/dist/lib/prisma.d.ts.map +0 -1
- package/dist/lib/storage.d.ts.map +0 -1
- package/dist/routers/_app.d.ts.map +0 -1
- package/dist/routers/auth.d.ts.map +0 -1
- package/dist/routers/sample.js +0 -21
- package/dist/routers/workspace.d.ts.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/trpc.d.ts.map +0 -1
package/dist/context.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
|
|
2
2
|
export declare function createContext({ req, res }: CreateExpressContextOptions): Promise<{
|
|
3
|
-
db: import("
|
|
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
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
|
9
|
-
//# sourceMappingURL=context.d.ts.map
|
package/dist/context.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { prisma } from "./lib/prisma.js";
|
|
2
|
+
import { verifyCustomAuthCookie } from "./lib/auth.js";
|
|
3
|
+
import cookie from "cookie";
|
|
4
|
+
export async function createContext({ req, res }) {
|
|
5
|
+
const cookies = cookie.parse(req.headers.cookie ?? "");
|
|
6
|
+
// Only use custom auth cookie
|
|
7
|
+
const custom = verifyCustomAuthCookie(cookies["auth_token"]);
|
|
8
|
+
if (custom) {
|
|
9
|
+
return { db: prisma, session: { user: { id: custom.userId } }, req, res, cookies };
|
|
10
|
+
}
|
|
11
|
+
return { db: prisma, session: null, req, res, cookies };
|
|
8
12
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export type { AppRouter } from "./routers/_app";
|
|
2
|
-
export type { RouterInputs } from "./routers/_app";
|
|
3
|
-
export type { RouterOutputs } from "./routers/_app";
|
|
4
|
-
//# sourceMappingURL=index.d.ts.map
|
|
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/dist/index.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
package/dist/lib/auth.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export declare
|
|
2
|
-
|
|
1
|
+
export declare function verifyCustomAuthCookie(cookieValue: string | undefined): {
|
|
2
|
+
userId: string;
|
|
3
|
+
} | null;
|
package/dist/lib/auth.js
CHANGED
|
@@ -1,37 +1,40 @@
|
|
|
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.authRouter = void 0;
|
|
7
1
|
// src/server/auth.ts
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
// Custom HMAC cookie: auth_token = base64(userId).hex(hmacSHA256(base64(userId), secret))
|
|
4
|
+
export function verifyCustomAuthCookie(cookieValue) {
|
|
5
|
+
if (!cookieValue)
|
|
6
|
+
return null;
|
|
7
|
+
const secret = process.env.CUSTOM_AUTH_SECRET;
|
|
8
|
+
if (!secret)
|
|
9
|
+
return null;
|
|
10
|
+
const parts = cookieValue.split(".");
|
|
11
|
+
if (parts.length !== 2)
|
|
12
|
+
return null;
|
|
13
|
+
const [base64UserId, signatureHex] = parts;
|
|
14
|
+
let userId;
|
|
15
|
+
try {
|
|
16
|
+
const buf = Buffer.from(base64UserId, "base64url");
|
|
17
|
+
userId = buf.toString("utf8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const hmac = crypto.createHmac("sha256", secret);
|
|
23
|
+
hmac.update(base64UserId);
|
|
24
|
+
const expected = hmac.digest("hex");
|
|
25
|
+
if (!timingSafeEqualHex(signatureHex, expected))
|
|
26
|
+
return null;
|
|
27
|
+
return { userId };
|
|
28
|
+
}
|
|
29
|
+
function timingSafeEqualHex(a, b) {
|
|
30
|
+
try {
|
|
31
|
+
const ab = Buffer.from(a, "hex");
|
|
32
|
+
const bb = Buffer.from(b, "hex");
|
|
33
|
+
if (ab.length !== bb.length)
|
|
34
|
+
return false;
|
|
35
|
+
return crypto.timingSafeEqual(ab, bb);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/lib/file.d.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=file.d.ts.map
|
package/dist/lib/prisma.d.ts
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
import { PrismaClient } from "@prisma/client";
|
|
2
|
-
export declare const prisma: PrismaClient<import("
|
|
3
|
-
//# sourceMappingURL=prisma.d.ts.map
|
|
2
|
+
export declare const prisma: PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
package/dist/lib/prisma.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.prisma = void 0;
|
|
4
|
-
const client_1 = require("@prisma/client");
|
|
1
|
+
import { PrismaClient } from "@prisma/client";
|
|
5
2
|
const globalForPrisma = globalThis;
|
|
6
|
-
|
|
7
|
-
new
|
|
3
|
+
export const prisma = globalForPrisma.prisma ??
|
|
4
|
+
new PrismaClient({
|
|
8
5
|
// log: ["query"], // enable if you want
|
|
9
6
|
});
|
|
10
7
|
if (process.env.NODE_ENV !== "production")
|
|
11
|
-
globalForPrisma.prisma =
|
|
8
|
+
globalForPrisma.prisma = prisma;
|
package/dist/lib/storage.d.ts
CHANGED
package/dist/lib/storage.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.bucket = exports.storage = void 0;
|
|
4
1
|
// src/server/lib/gcs.ts
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { Storage } from "@google-cloud/storage";
|
|
3
|
+
export const storage = new Storage({
|
|
7
4
|
projectId: process.env.GCP_PROJECT_ID,
|
|
8
5
|
credentials: {
|
|
9
6
|
client_email: process.env.GCP_CLIENT_EMAIL,
|
|
10
7
|
private_key: process.env.GCP_PRIVATE_KEY?.replace(/\\n/g, "\n"),
|
|
11
8
|
},
|
|
12
9
|
});
|
|
13
|
-
|
|
10
|
+
export const bucket = storage.bucket(process.env.GCP_BUCKET);
|
package/dist/routers/_app.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
|
|
2
2
|
export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
3
3
|
ctx: {
|
|
4
|
-
db: import("
|
|
4
|
+
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
5
5
|
session: any;
|
|
6
6
|
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
7
7
|
res: import("express").Response<any, Record<string, any>>;
|
|
8
|
+
cookies: Record<string, string | undefined>;
|
|
8
9
|
};
|
|
9
10
|
meta: object;
|
|
10
11
|
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
@@ -12,10 +13,11 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
12
13
|
}, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
|
|
13
14
|
auth: import("@trpc/server").TRPCBuiltRouter<{
|
|
14
15
|
ctx: {
|
|
15
|
-
db: import("
|
|
16
|
+
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
16
17
|
session: any;
|
|
17
18
|
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
18
19
|
res: import("express").Response<any, Record<string, any>>;
|
|
20
|
+
cookies: Record<string, string | undefined>;
|
|
19
21
|
};
|
|
20
22
|
meta: object;
|
|
21
23
|
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
@@ -34,13 +36,40 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
34
36
|
};
|
|
35
37
|
meta: object;
|
|
36
38
|
}>;
|
|
39
|
+
login: import("@trpc/server").TRPCMutationProcedure<{
|
|
40
|
+
input: {
|
|
41
|
+
email: string;
|
|
42
|
+
password: string;
|
|
43
|
+
};
|
|
44
|
+
output: {
|
|
45
|
+
id: string;
|
|
46
|
+
email: string | null;
|
|
47
|
+
name: string | null;
|
|
48
|
+
};
|
|
49
|
+
meta: object;
|
|
50
|
+
}>;
|
|
51
|
+
getSession: import("@trpc/server").TRPCQueryProcedure<{
|
|
52
|
+
input: void;
|
|
53
|
+
output: {
|
|
54
|
+
id: string;
|
|
55
|
+
userId: string;
|
|
56
|
+
user: {
|
|
57
|
+
id: string;
|
|
58
|
+
email: string | null;
|
|
59
|
+
name: string | null;
|
|
60
|
+
image: string | null;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
meta: object;
|
|
64
|
+
}>;
|
|
37
65
|
}>>;
|
|
38
66
|
workspace: import("@trpc/server").TRPCBuiltRouter<{
|
|
39
67
|
ctx: {
|
|
40
|
-
db: import("
|
|
68
|
+
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
41
69
|
session: any;
|
|
42
70
|
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
43
71
|
res: import("express").Response<any, Record<string, any>>;
|
|
72
|
+
cookies: Record<string, string | undefined>;
|
|
44
73
|
};
|
|
45
74
|
meta: object;
|
|
46
75
|
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
@@ -134,7 +163,7 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
134
163
|
fileId: string[];
|
|
135
164
|
id: string;
|
|
136
165
|
};
|
|
137
|
-
output: import("
|
|
166
|
+
output: import("@prisma/client").Prisma.BatchPayload;
|
|
138
167
|
meta: object;
|
|
139
168
|
}>;
|
|
140
169
|
}>>;
|
|
@@ -142,4 +171,3 @@ export declare const appRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
142
171
|
export type AppRouter = typeof appRouter;
|
|
143
172
|
export type RouterInputs = inferRouterInputs<AppRouter>;
|
|
144
173
|
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
|
145
|
-
//# sourceMappingURL=_app.d.ts.map
|
package/dist/routers/_app.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
exports.appRouter = (0, trpc_1.router)({
|
|
8
|
-
auth: auth_1.auth,
|
|
9
|
-
workspace: workspace_1.workspace
|
|
1
|
+
import { router } from '../trpc.js';
|
|
2
|
+
import { auth } from './auth.js';
|
|
3
|
+
import { workspace } from './workspace.js';
|
|
4
|
+
export const appRouter = router({
|
|
5
|
+
auth,
|
|
6
|
+
workspace
|
|
10
7
|
});
|
package/dist/routers/auth.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export declare const auth: import("@trpc/server").TRPCBuiltRouter<{
|
|
2
2
|
ctx: {
|
|
3
|
-
db: import("
|
|
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;
|
|
@@ -22,5 +23,30 @@ export declare const auth: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
22
23
|
};
|
|
23
24
|
meta: object;
|
|
24
25
|
}>;
|
|
26
|
+
login: import("@trpc/server").TRPCMutationProcedure<{
|
|
27
|
+
input: {
|
|
28
|
+
email: string;
|
|
29
|
+
password: string;
|
|
30
|
+
};
|
|
31
|
+
output: {
|
|
32
|
+
id: string;
|
|
33
|
+
email: string | null;
|
|
34
|
+
name: string | null;
|
|
35
|
+
};
|
|
36
|
+
meta: object;
|
|
37
|
+
}>;
|
|
38
|
+
getSession: import("@trpc/server").TRPCQueryProcedure<{
|
|
39
|
+
input: void;
|
|
40
|
+
output: {
|
|
41
|
+
id: string;
|
|
42
|
+
userId: string;
|
|
43
|
+
user: {
|
|
44
|
+
id: string;
|
|
45
|
+
email: string | null;
|
|
46
|
+
name: string | null;
|
|
47
|
+
image: string | null;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
meta: object;
|
|
51
|
+
}>;
|
|
25
52
|
}>>;
|
|
26
|
-
//# sourceMappingURL=auth.d.ts.map
|
package/dist/routers/auth.js
CHANGED
|
@@ -1,18 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
exports.auth = (0, trpc_1.router)({
|
|
11
|
-
signup: trpc_1.publicProcedure
|
|
12
|
-
.input(zod_1.z.object({
|
|
13
|
-
name: zod_1.z.string().min(1),
|
|
14
|
-
email: zod_1.z.string().email(),
|
|
15
|
-
password: zod_1.z.string().min(6),
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { router, publicProcedure } from '../trpc.js';
|
|
3
|
+
import bcrypt from 'bcryptjs';
|
|
4
|
+
export const auth = router({
|
|
5
|
+
signup: publicProcedure
|
|
6
|
+
.input(z.object({
|
|
7
|
+
name: z.string().min(1),
|
|
8
|
+
email: z.string().email(),
|
|
9
|
+
password: z.string().min(6),
|
|
16
10
|
}))
|
|
17
11
|
.mutation(async ({ ctx, input }) => {
|
|
18
12
|
const existing = await ctx.db.user.findUnique({
|
|
@@ -21,7 +15,7 @@ exports.auth = (0, trpc_1.router)({
|
|
|
21
15
|
if (existing) {
|
|
22
16
|
throw new Error("Email already registered");
|
|
23
17
|
}
|
|
24
|
-
const hash = await
|
|
18
|
+
const hash = await bcrypt.hash(input.password, 10);
|
|
25
19
|
const user = await ctx.db.user.create({
|
|
26
20
|
data: {
|
|
27
21
|
name: input.name,
|
|
@@ -32,4 +26,42 @@ exports.auth = (0, trpc_1.router)({
|
|
|
32
26
|
});
|
|
33
27
|
return { id: user.id, email: user.email, name: user.name };
|
|
34
28
|
}),
|
|
29
|
+
login: publicProcedure
|
|
30
|
+
.input(z.object({
|
|
31
|
+
email: z.string().email(),
|
|
32
|
+
password: z.string().min(6),
|
|
33
|
+
}))
|
|
34
|
+
.mutation(async ({ ctx, input }) => {
|
|
35
|
+
const user = await ctx.db.user.findUnique({
|
|
36
|
+
where: { email: input.email },
|
|
37
|
+
});
|
|
38
|
+
if (!user) {
|
|
39
|
+
throw new Error("Invalid credentials");
|
|
40
|
+
}
|
|
41
|
+
const valid = await bcrypt.compare(input.password, user.passwordHash);
|
|
42
|
+
if (!valid) {
|
|
43
|
+
throw new Error("Invalid credentials");
|
|
44
|
+
}
|
|
45
|
+
return { id: user.id, email: user.email, name: user.name };
|
|
46
|
+
}),
|
|
47
|
+
getSession: publicProcedure.query(async ({ ctx }) => {
|
|
48
|
+
const session = await ctx.db.session.findUnique({
|
|
49
|
+
where: {
|
|
50
|
+
id: ctx.session?.id,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
if (!session) {
|
|
54
|
+
throw new Error("Session not found");
|
|
55
|
+
}
|
|
56
|
+
if (session.expires < new Date()) {
|
|
57
|
+
throw new Error("Session expired");
|
|
58
|
+
}
|
|
59
|
+
const user = await ctx.db.user.findUnique({
|
|
60
|
+
where: { id: session.userId },
|
|
61
|
+
});
|
|
62
|
+
if (!user) {
|
|
63
|
+
throw new Error("User not found");
|
|
64
|
+
}
|
|
65
|
+
return { id: session.id, userId: session.userId, user: { id: user.id, email: user.email, name: user.name, image: user.image } };
|
|
66
|
+
}),
|
|
35
67
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export declare const workspace: import("@trpc/server").TRPCBuiltRouter<{
|
|
2
2
|
ctx: {
|
|
3
|
-
db: import("
|
|
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;
|
|
@@ -97,8 +98,7 @@ export declare const workspace: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
97
98
|
fileId: string[];
|
|
98
99
|
id: string;
|
|
99
100
|
};
|
|
100
|
-
output: import("
|
|
101
|
+
output: import("@prisma/client").Prisma.BatchPayload;
|
|
101
102
|
meta: object;
|
|
102
103
|
}>;
|
|
103
104
|
}>>;
|
|
104
|
-
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const trpc_1 = require("../trpc");
|
|
6
|
-
const storage_1 = require("src/lib/storage");
|
|
7
|
-
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({
|
|
8
5
|
// Mutation with Zod input
|
|
9
|
-
list:
|
|
6
|
+
list: authedProcedure
|
|
10
7
|
.query(async ({ ctx, input }) => {
|
|
11
8
|
const workspaces = await ctx.db.workspace.findMany({
|
|
12
9
|
where: {
|
|
@@ -15,10 +12,10 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
15
12
|
});
|
|
16
13
|
return workspaces;
|
|
17
14
|
}),
|
|
18
|
-
create:
|
|
19
|
-
.input(
|
|
20
|
-
name:
|
|
21
|
-
description:
|
|
15
|
+
create: authedProcedure
|
|
16
|
+
.input(z.object({
|
|
17
|
+
name: z.string().min(1).max(100),
|
|
18
|
+
description: z.string().max(500).optional(),
|
|
22
19
|
}))
|
|
23
20
|
.mutation(({ ctx, input }) => {
|
|
24
21
|
return ctx.db.workspace.create({
|
|
@@ -29,9 +26,9 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
29
26
|
},
|
|
30
27
|
});
|
|
31
28
|
}),
|
|
32
|
-
get:
|
|
33
|
-
.input(
|
|
34
|
-
id:
|
|
29
|
+
get: authedProcedure
|
|
30
|
+
.input(z.object({
|
|
31
|
+
id: z.string().uuid(),
|
|
35
32
|
}))
|
|
36
33
|
.query(({ ctx, input }) => {
|
|
37
34
|
return ctx.db.workspace.findUnique({
|
|
@@ -40,11 +37,11 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
40
37
|
},
|
|
41
38
|
});
|
|
42
39
|
}),
|
|
43
|
-
update:
|
|
44
|
-
.input(
|
|
45
|
-
id:
|
|
46
|
-
name:
|
|
47
|
-
description:
|
|
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(),
|
|
48
45
|
}))
|
|
49
46
|
.mutation(({ ctx, input }) => {
|
|
50
47
|
return ctx.db.workspace.update({
|
|
@@ -57,9 +54,9 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
57
54
|
},
|
|
58
55
|
});
|
|
59
56
|
}),
|
|
60
|
-
delete:
|
|
61
|
-
.input(
|
|
62
|
-
id:
|
|
57
|
+
delete: authedProcedure
|
|
58
|
+
.input(z.object({
|
|
59
|
+
id: z.string().uuid(),
|
|
63
60
|
}))
|
|
64
61
|
.mutation(({ ctx, input }) => {
|
|
65
62
|
ctx.db.workspace.delete({
|
|
@@ -69,13 +66,13 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
69
66
|
});
|
|
70
67
|
return true;
|
|
71
68
|
}),
|
|
72
|
-
uploadFiles:
|
|
73
|
-
.input(
|
|
74
|
-
id:
|
|
75
|
-
files:
|
|
76
|
-
filename:
|
|
77
|
-
contentType:
|
|
78
|
-
size:
|
|
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
|
|
79
76
|
})),
|
|
80
77
|
}))
|
|
81
78
|
.mutation(async ({ ctx, input }) => {
|
|
@@ -92,7 +89,7 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
92
89
|
},
|
|
93
90
|
});
|
|
94
91
|
// 2. Generate signed URL for direct upload
|
|
95
|
-
const [url] = await
|
|
92
|
+
const [url] = await bucket
|
|
96
93
|
.file(`${ctx.session.user.id}/${record.id}-${file.filename}`)
|
|
97
94
|
.getSignedUrl({
|
|
98
95
|
action: "write",
|
|
@@ -103,7 +100,7 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
103
100
|
await ctx.db.fileAsset.update({
|
|
104
101
|
where: { id: record.id },
|
|
105
102
|
data: {
|
|
106
|
-
bucket:
|
|
103
|
+
bucket: bucket.name,
|
|
107
104
|
objectKey: `${ctx.session.user.id}/${record.id}-${file.filename}`,
|
|
108
105
|
},
|
|
109
106
|
});
|
|
@@ -114,10 +111,10 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
114
111
|
}
|
|
115
112
|
return results;
|
|
116
113
|
}),
|
|
117
|
-
deleteFiles:
|
|
118
|
-
.input(
|
|
119
|
-
fileId:
|
|
120
|
-
id:
|
|
114
|
+
deleteFiles: authedProcedure
|
|
115
|
+
.input(z.object({
|
|
116
|
+
fileId: z.array(z.string().uuid()),
|
|
117
|
+
id: z.string().uuid(),
|
|
121
118
|
}))
|
|
122
119
|
.mutation(({ ctx, input }) => {
|
|
123
120
|
const files = ctx.db.fileAsset.findMany({
|
|
@@ -130,7 +127,7 @@ exports.workspace = (0, trpc_1.router)({
|
|
|
130
127
|
files.then((fileRecords) => {
|
|
131
128
|
fileRecords.forEach((file) => {
|
|
132
129
|
if (file.bucket && file.objectKey) {
|
|
133
|
-
const gcsFile =
|
|
130
|
+
const gcsFile = bucket.file(file.objectKey);
|
|
134
131
|
gcsFile.delete({ ignoreNotFound: true }).catch((err) => {
|
|
135
132
|
console.error(`Error deleting file ${file.objectKey} from bucket ${file.bucket}:`, err);
|
|
136
133
|
});
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,71 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 = (
|
|
11
|
+
const app = express();
|
|
51
12
|
// Middlewares
|
|
52
|
-
app.use((
|
|
53
|
-
app.use((
|
|
13
|
+
app.use(helmet());
|
|
14
|
+
app.use(cors({
|
|
54
15
|
origin: "http://localhost:3000", // your Next.js dev URL
|
|
55
16
|
credentials: true, // allow cookies
|
|
56
17
|
}));
|
|
57
|
-
app.use((
|
|
58
|
-
app.use((
|
|
59
|
-
app.use(
|
|
60
|
-
app.use("/auth", auth_1.authRouter); // Auth routes live under /auth/*
|
|
18
|
+
app.use(morgan('dev'));
|
|
19
|
+
app.use(compression());
|
|
20
|
+
app.use(express.json());
|
|
61
21
|
// Health (plain Express)
|
|
62
22
|
app.get('/', (_req, res) => {
|
|
63
23
|
res.json({ ok: true, service: 'trpc-express', ts: Date.now() });
|
|
64
24
|
});
|
|
65
25
|
// tRPC mounted under /trpc
|
|
66
26
|
app.use('/trpc', trpcExpress.createExpressMiddleware({
|
|
67
|
-
router:
|
|
68
|
-
createContext
|
|
27
|
+
router: appRouter,
|
|
28
|
+
createContext,
|
|
69
29
|
}));
|
|
70
30
|
app.listen(PORT, () => {
|
|
71
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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(".prisma/client").PrismaClient<import(".prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 =
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
+
export const authedProcedure = publicProcedure.use(isAuthed);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goscribe/server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
"@google-cloud/storage": "^7.17.0",
|
|
25
25
|
"@prisma/client": "^6.14.0",
|
|
26
26
|
"@trpc/server": "^11.5.0",
|
|
27
|
+
"@types/cookie": "^1.0.0",
|
|
27
28
|
"bcryptjs": "^3.0.2",
|
|
28
29
|
"compression": "^1.8.1",
|
|
30
|
+
"cookie": "^1.0.2",
|
|
29
31
|
"cors": "^2.8.5",
|
|
30
32
|
"express": "^5.1.0",
|
|
31
33
|
"helmet": "^8.1.0",
|
package/prisma/schema.prisma
CHANGED
|
@@ -6,6 +6,7 @@ generator client {
|
|
|
6
6
|
datasource db {
|
|
7
7
|
provider = "postgresql"
|
|
8
8
|
url = env("DATABASE_URL")
|
|
9
|
+
directUrl = env("DIRECT_URL") // for shadow db in migrations
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
//
|
|
@@ -35,6 +36,7 @@ model User {
|
|
|
35
36
|
emailVerified DateTime?
|
|
36
37
|
passwordHash String? // for credentials login
|
|
37
38
|
image String?
|
|
39
|
+
session Session[]
|
|
38
40
|
|
|
39
41
|
// Ownership
|
|
40
42
|
folders Folder[] @relation("UserFolders")
|
|
@@ -43,30 +45,15 @@ model User {
|
|
|
43
45
|
artifacts Artifact[] @relation("UserArtifacts")
|
|
44
46
|
versions ArtifactVersion[] @relation("UserArtifactVersions")
|
|
45
47
|
|
|
46
|
-
accounts Account[]
|
|
47
|
-
|
|
48
48
|
createdAt DateTime @default(now())
|
|
49
49
|
updatedAt DateTime @updatedAt
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
model
|
|
53
|
-
id
|
|
54
|
-
userId
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
providerAccountId String
|
|
58
|
-
|
|
59
|
-
refresh_token String?
|
|
60
|
-
access_token String?
|
|
61
|
-
expires_at Int?
|
|
62
|
-
token_type String?
|
|
63
|
-
scope String?
|
|
64
|
-
id_token String?
|
|
65
|
-
session_state String?
|
|
66
|
-
|
|
67
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
68
|
-
|
|
69
|
-
@@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
|
|
70
57
|
}
|
|
71
58
|
|
|
72
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
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
|
|
7
|
-
|
|
8
|
-
|
|
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/lib/auth.ts
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
// src/server/auth.ts
|
|
2
|
-
import
|
|
3
|
-
import { PrismaAdapter } from "@auth/prisma-adapter";
|
|
4
|
-
import { prisma } from "../lib/prisma.js";
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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/routers/auth.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { router, publicProcedure, authedProcedure } from '../trpc.js';
|
|
|
3
3
|
import bcrypt from 'bcryptjs';
|
|
4
4
|
|
|
5
5
|
export const auth = router({
|
|
6
|
-
signup: publicProcedure
|
|
6
|
+
signup: publicProcedure
|
|
7
7
|
.input(z.object({
|
|
8
8
|
name: z.string().min(1),
|
|
9
9
|
email: z.string().email(),
|
|
@@ -30,5 +30,50 @@ signup: publicProcedure
|
|
|
30
30
|
|
|
31
31
|
return { id: user.id, email: user.email, name: user.name };
|
|
32
32
|
}),
|
|
33
|
+
login: publicProcedure
|
|
34
|
+
.input(z.object({
|
|
35
|
+
email: z.string().email(),
|
|
36
|
+
password: z.string().min(6),
|
|
37
|
+
}))
|
|
38
|
+
.mutation(async ({ ctx, input }) => {
|
|
39
|
+
const user = await ctx.db.user.findUnique({
|
|
40
|
+
where: { email: input.email },
|
|
41
|
+
});
|
|
42
|
+
if (!user) {
|
|
43
|
+
throw new Error("Invalid credentials");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const valid = await bcrypt.compare(input.password, user.passwordHash!);
|
|
47
|
+
if (!valid) {
|
|
48
|
+
throw new Error("Invalid credentials");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { id: user.id, email: user.email, name: user.name };
|
|
52
|
+
}),
|
|
53
|
+
getSession: publicProcedure.query(async ({ ctx }) => {
|
|
54
|
+
const session = await ctx.db.session.findUnique({
|
|
55
|
+
where: {
|
|
56
|
+
id: ctx.session?.id,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!session) {
|
|
61
|
+
throw new Error("Session not found");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (session.expires < new Date()) {
|
|
65
|
+
throw new Error("Session expired");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const user = await ctx.db.user.findUnique({
|
|
69
|
+
where: { id: session.userId },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (!user) {
|
|
73
|
+
throw new Error("User not found");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { id: session.id, userId: session.userId, user: { id: user.id, email: user.email, name: user.name, image: user.image } };
|
|
77
|
+
}),
|
|
33
78
|
});
|
|
34
79
|
|
package/src/routers/workspace.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { FileAsset } from '@prisma/client';
|
|
|
5
5
|
|
|
6
6
|
export const workspace = router({
|
|
7
7
|
// Mutation with Zod input
|
|
8
|
-
list:
|
|
8
|
+
list: authedProcedure
|
|
9
9
|
.query(async ({ ctx, input }) => {
|
|
10
10
|
const workspaces = await ctx.db.workspace.findMany({
|
|
11
11
|
where: {
|
|
@@ -15,7 +15,7 @@ export const workspace = router({
|
|
|
15
15
|
return workspaces;
|
|
16
16
|
}),
|
|
17
17
|
|
|
18
|
-
create:
|
|
18
|
+
create: authedProcedure
|
|
19
19
|
.input(z.object({
|
|
20
20
|
name: z.string().min(1).max(100),
|
|
21
21
|
description: z.string().max(500).optional(),
|
|
@@ -29,7 +29,7 @@ export const workspace = router({
|
|
|
29
29
|
},
|
|
30
30
|
});
|
|
31
31
|
}),
|
|
32
|
-
get:
|
|
32
|
+
get: authedProcedure
|
|
33
33
|
.input(z.object({
|
|
34
34
|
id: z.string().uuid(),
|
|
35
35
|
}))
|
|
@@ -40,7 +40,7 @@ export const workspace = router({
|
|
|
40
40
|
},
|
|
41
41
|
});
|
|
42
42
|
}),
|
|
43
|
-
update:
|
|
43
|
+
update: authedProcedure
|
|
44
44
|
.input(z.object({
|
|
45
45
|
id: z.string().uuid(),
|
|
46
46
|
name: z.string().min(1).max(100).optional(),
|
|
@@ -57,7 +57,7 @@ export const workspace = router({
|
|
|
57
57
|
},
|
|
58
58
|
});
|
|
59
59
|
}),
|
|
60
|
-
delete:
|
|
60
|
+
delete: authedProcedure
|
|
61
61
|
.input(z.object({
|
|
62
62
|
id: z.string().uuid(),
|
|
63
63
|
}))
|
|
@@ -69,7 +69,7 @@ export const workspace = router({
|
|
|
69
69
|
});
|
|
70
70
|
return true;
|
|
71
71
|
}),
|
|
72
|
-
uploadFiles:
|
|
72
|
+
uploadFiles: authedProcedure
|
|
73
73
|
.input(z.object({
|
|
74
74
|
id: z.string().uuid(),
|
|
75
75
|
files: z.array(
|
|
@@ -122,7 +122,7 @@ export const workspace = router({
|
|
|
122
122
|
return results;
|
|
123
123
|
|
|
124
124
|
}),
|
|
125
|
-
deleteFiles:
|
|
125
|
+
deleteFiles: authedProcedure
|
|
126
126
|
.input(z.object({
|
|
127
127
|
fileId: z.array(z.string().uuid()),
|
|
128
128
|
id: z.string().uuid(),
|
package/src/server.ts
CHANGED
|
@@ -4,7 +4,6 @@ import helmet from 'helmet';
|
|
|
4
4
|
import morgan from 'morgan';
|
|
5
5
|
import compression from 'compression';
|
|
6
6
|
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
7
|
-
import { authRouter } from './lib/auth.js';
|
|
8
7
|
|
|
9
8
|
import { appRouter } from './routers/_app.js';
|
|
10
9
|
import { createContext } from './context.js';
|
|
@@ -25,8 +24,6 @@ async function main() {
|
|
|
25
24
|
app.use(compression());
|
|
26
25
|
app.use(express.json());
|
|
27
26
|
|
|
28
|
-
app.use("/auth", authRouter); // Auth routes live under /auth/*
|
|
29
|
-
|
|
30
27
|
|
|
31
28
|
// Health (plain Express)
|
|
32
29
|
app.get('/', (_req, res) => {
|
package/src/trpc.ts
CHANGED
|
@@ -15,20 +15,14 @@ export const publicProcedure = t.procedure;
|
|
|
15
15
|
|
|
16
16
|
/** Middleware that enforces authentication */
|
|
17
17
|
const isAuthed = middleware(({ ctx, next }) => {
|
|
18
|
-
|
|
18
|
+
const hasUser = Boolean((ctx.session as any)?.user?.id);
|
|
19
|
+
if (!ctx.session || !hasUser) {
|
|
19
20
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
20
21
|
}
|
|
22
|
+
|
|
21
23
|
return next({
|
|
22
24
|
ctx: {
|
|
23
|
-
|
|
24
|
-
// refine ctx: session is guaranteed, user.id is string
|
|
25
|
-
session: {
|
|
26
|
-
...ctx.session,
|
|
27
|
-
user: {
|
|
28
|
-
...ctx.session.user,
|
|
29
|
-
id: ctx.session.user.id, // typed non-null
|
|
30
|
-
},
|
|
31
|
-
},
|
|
25
|
+
session: ctx.session,
|
|
32
26
|
},
|
|
33
27
|
});
|
|
34
28
|
});
|
package/dist/context.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAGjF,wBAAsB,aAAa,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,2BAA2B;;;;;GAI5E;AAED,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/lib/auth.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,UAAU,0HA0BnB,CAAA"}
|
package/dist/lib/file.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/lib/file.ts"],"names":[],"mappings":""}
|
package/dist/lib/prisma.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prisma.d.ts","sourceRoot":"","sources":["../../src/lib/prisma.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAI9C,eAAO,MAAM,MAAM,gIAIf,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/lib/storage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,eAAO,MAAM,OAAO,SAMlB,CAAC;AAEH,eAAO,MAAM,MAAM,wCAA0C,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"_app.d.ts","sourceRoot":"","sources":["../../src/routers/_app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKrE,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAGpB,CAAC;AAGH,MAAM,MAAM,SAAS,GAAG,OAAO,SAAS,CAAC;AACzC,MAAM,MAAM,YAAY,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/routers/auth.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;GA4Bf,CAAC"}
|
package/dist/routers/sample.js
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sampleRouter = void 0;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
const trpc_1 = require("../trpc");
|
|
6
|
-
exports.sampleRouter = (0, trpc_1.router)({
|
|
7
|
-
// GET-like: query without input
|
|
8
|
-
hello: trpc_1.publicProcedure.query(() => {
|
|
9
|
-
return { message: 'Hello from tRPC + Express 👋' };
|
|
10
|
-
}),
|
|
11
|
-
// Mutation with Zod input
|
|
12
|
-
echo: trpc_1.publicProcedure
|
|
13
|
-
.input(zod_1.z.object({ text: zod_1.z.string().min(1) }))
|
|
14
|
-
.mutation(({ input }) => {
|
|
15
|
-
return { echoed: input.text };
|
|
16
|
-
}),
|
|
17
|
-
// Authed query
|
|
18
|
-
me: trpc_1.authedProcedure.query(({ ctx }) => {
|
|
19
|
-
return { userId: ctx.user.id, role: ctx.user.role };
|
|
20
|
-
}),
|
|
21
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/routers/workspace.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuJpB,CAAC"}
|
package/dist/server.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":""}
|
package/dist/trpc.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"trpc.d.ts","sourceRoot":"","sources":["../src/trpc.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,MAAM;;;;;;;;;;EAAW,CAAC;AAC/B,eAAO,MAAM,UAAU;;;;;;;;;;sCAAe,CAAC;AACvC,eAAO,MAAM,eAAe;;;;;yLAAc,CAAC;AAsB3C,gCAAgC;AAChC,eAAO,MAAM,eAAe;;;;;;;;;;yKAAgC,CAAC"}
|