@goscribe/server 1.3.4 → 1.5.0
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/.env.example +12 -0
- package/.vscode/settings.json +3 -0
- package/REFACTOR_NOTES.md +60 -0
- package/TESTING_PROMPT.md +225 -0
- package/dist/controllers/admin.controller.d.ts +715 -0
- package/dist/controllers/admin.controller.js +9 -0
- package/dist/controllers/annotations.controller.d.ts +439 -0
- package/dist/controllers/annotations.controller.js +9 -0
- package/dist/controllers/app-router.controller.d.ts +3011 -0
- package/dist/controllers/app-router.controller.js +38 -0
- package/dist/controllers/app-router.controller.test.d.ts +1 -0
- package/dist/controllers/app-router.controller.test.js +36 -0
- package/dist/controllers/auth.controller.d.ts +323 -0
- package/dist/controllers/auth.controller.js +9 -0
- package/dist/controllers/base.controller.d.ts +4 -0
- package/dist/controllers/base.controller.js +5 -0
- package/dist/controllers/chat.controller.d.ts +341 -0
- package/dist/controllers/chat.controller.js +9 -0
- package/dist/controllers/copilot.controller.d.ts +397 -0
- package/dist/controllers/copilot.controller.js +9 -0
- package/dist/controllers/flashcards.controller.d.ts +651 -0
- package/dist/controllers/flashcards.controller.js +9 -0
- package/dist/controllers/members.controller.d.ts +339 -0
- package/dist/controllers/members.controller.js +9 -0
- package/dist/controllers/notifications.controller.d.ts +199 -0
- package/dist/controllers/notifications.controller.js +9 -0
- package/dist/controllers/payment.controller.d.ts +181 -0
- package/dist/controllers/payment.controller.js +9 -0
- package/dist/controllers/podcast.controller.d.ts +575 -0
- package/dist/controllers/podcast.controller.js +9 -0
- package/dist/controllers/router-module.controller.d.ts +5 -0
- package/dist/controllers/router-module.controller.js +6 -0
- package/dist/controllers/studyguide.controller.d.ts +73 -0
- package/dist/controllers/studyguide.controller.js +9 -0
- package/dist/controllers/worksheets.controller.d.ts +829 -0
- package/dist/controllers/worksheets.controller.js +9 -0
- package/dist/controllers/workspace.controller.d.ts +1207 -0
- package/dist/controllers/workspace.controller.js +9 -0
- package/dist/lib/activity_human_description.test.js +16 -15
- package/dist/lib/activity_log_service.test.js +28 -23
- package/dist/lib/ai/config.d.ts +20 -0
- package/dist/lib/ai/config.js +31 -0
- package/dist/lib/ai/embedding-client.d.ts +8 -0
- package/dist/lib/ai/embedding-client.js +30 -0
- package/dist/lib/ai/index.d.ts +47 -0
- package/dist/lib/ai/index.js +28 -0
- package/dist/lib/ai/inference-backend/client.d.ts +28 -0
- package/dist/lib/ai/inference-backend/client.js +301 -0
- package/dist/lib/ai/inference-backend/mocks.d.ts +12 -0
- package/dist/lib/ai/inference-backend/mocks.js +133 -0
- package/dist/lib/ai/inference-backend/types.d.ts +44 -0
- package/dist/lib/ai/inference-backend/types.js +1 -0
- package/dist/lib/ai/json-parse.d.ts +2 -0
- package/dist/lib/ai/json-parse.js +34 -0
- package/dist/lib/ai/llm-client.d.ts +6 -0
- package/dist/lib/ai/llm-client.js +19 -0
- package/dist/lib/ai/mock.d.ts +2 -0
- package/dist/lib/ai/mock.js +10 -0
- package/dist/lib/ai/types.d.ts +9 -0
- package/dist/lib/ai/types.js +1 -0
- package/dist/lib/chunking.d.ts +19 -0
- package/dist/lib/chunking.js +47 -0
- package/dist/lib/curated-kb-seed.d.ts +12 -0
- package/dist/lib/curated-kb-seed.js +155 -0
- package/dist/lib/email.js +67 -108
- package/dist/lib/embeddings.d.ts +2 -0
- package/dist/lib/embeddings.js +1 -0
- package/dist/lib/ensure-curated-kb-catalog.d.ts +6 -0
- package/dist/lib/ensure-curated-kb-catalog.js +53 -0
- package/dist/lib/env.d.ts +1 -5
- package/dist/lib/env.js +2 -7
- package/dist/lib/inference.d.ts +1 -8
- package/dist/lib/inference.js +1 -19
- package/dist/lib/kb-meta.d.ts +8 -0
- package/dist/lib/kb-meta.js +77 -0
- package/dist/lib/note-text.d.ts +1 -0
- package/dist/lib/note-text.js +47 -0
- package/dist/lib/notification-service.test.js +37 -36
- package/dist/lib/pdf.d.ts +11 -0
- package/dist/lib/pdf.js +11 -0
- package/dist/lib/usage_service.d.ts +2 -1
- package/dist/lib/usage_service.js +30 -12
- package/dist/lib/worksheet-generation.js +4 -4
- package/dist/lib/worksheet-generation.test.js +32 -17
- package/dist/lib/workspace-kb.d.ts +5 -0
- package/dist/lib/workspace-kb.js +7 -0
- package/dist/models/controller-context.model.d.ts +8 -0
- package/dist/models/controller-context.model.js +1 -0
- package/dist/repositories/artifact.repository.d.ts +60 -0
- package/dist/repositories/artifact.repository.js +40 -0
- package/dist/repositories/base.repository.d.ts +14 -0
- package/dist/repositories/base.repository.js +14 -0
- package/dist/repositories/invitation.repository.d.ts +94 -0
- package/dist/repositories/invitation.repository.js +44 -0
- package/dist/repositories/notification.repository.d.ts +72 -0
- package/dist/repositories/notification.repository.js +44 -0
- package/dist/repositories/router-module.repository.d.ts +10 -0
- package/dist/repositories/router-module.repository.js +14 -0
- package/dist/repositories/user.repository.d.ts +74 -0
- package/dist/repositories/user.repository.js +37 -0
- package/dist/repositories/workspace-member.repository.d.ts +31 -0
- package/dist/repositories/workspace-member.repository.js +31 -0
- package/dist/repositories/workspace.repository.d.ts +97 -0
- package/dist/repositories/workspace.repository.js +79 -0
- package/dist/routers/_app.d.ts +528 -33
- package/dist/routers/_app.js +4 -0
- package/dist/routers/admin.d.ts +0 -4
- package/dist/routers/admin.js +21 -549
- package/dist/routers/annotations.js +12 -170
- package/dist/routers/artifactVersions.d.ts +65 -0
- package/dist/routers/artifactVersions.js +14 -0
- package/dist/routers/auth.d.ts +0 -6
- package/dist/routers/auth.js +36 -421
- package/dist/routers/chat.js +15 -229
- package/dist/routers/copilot.d.ts +14 -13
- package/dist/routers/copilot.js +13 -532
- package/dist/routers/flashcards.d.ts +5 -5
- package/dist/routers/flashcards.js +23 -349
- package/dist/routers/knowledgeBase.d.ts +421 -0
- package/dist/routers/knowledgeBase.js +118 -0
- package/dist/routers/members.d.ts +0 -41
- package/dist/routers/members.js +22 -710
- package/dist/routers/notes.d.ts +94 -0
- package/dist/routers/notes.js +37 -0
- package/dist/routers/notifications.js +7 -109
- package/dist/routers/payment.d.ts +3 -2
- package/dist/routers/payment.js +11 -393
- package/dist/routers/podcast.d.ts +1 -1
- package/dist/routers/podcast.js +11 -784
- package/dist/routers/studyguide.js +3 -129
- package/dist/routers/worksheets.d.ts +29 -14
- package/dist/routers/worksheets.js +49 -628
- package/dist/routers/workspace.d.ts +0 -4
- package/dist/routers/workspace.js +28 -922
- package/dist/scripts/purge-deleted-users.js +2 -2
- package/dist/server.js +10 -3
- package/dist/services/activity/activity-human-description.service.d.ts +13 -0
- package/dist/services/activity/activity-human-description.service.js +221 -0
- package/dist/services/activity/activity-human-description.service.test.d.ts +1 -0
- package/dist/services/activity/activity-human-description.service.test.js +16 -0
- package/dist/services/activity/activity-log.service.d.ts +87 -0
- package/dist/services/activity/activity-log.service.js +276 -0
- package/dist/services/activity/activity-log.service.test.d.ts +1 -0
- package/dist/services/activity/activity-log.service.test.js +27 -0
- package/dist/services/activity-human-description.service.d.ts +13 -0
- package/dist/services/activity-human-description.service.js +221 -0
- package/dist/services/activity-human-description.service.test.d.ts +1 -0
- package/dist/services/activity-human-description.service.test.js +16 -0
- package/dist/services/activity-log.service.d.ts +87 -0
- package/dist/services/activity-log.service.js +276 -0
- package/dist/services/activity-log.service.test.d.ts +1 -0
- package/dist/services/activity-log.service.test.js +27 -0
- package/dist/services/admin/admin.service.d.ts +270 -0
- package/dist/services/admin/admin.service.js +476 -0
- package/dist/services/admin.service.d.ts +270 -0
- package/dist/services/admin.service.js +476 -0
- package/dist/services/ai/ai-session.service.d.ts +5 -0
- package/dist/services/ai/ai-session.service.js +4 -0
- package/dist/services/ai-session.service.d.ts +60 -0
- package/dist/services/ai-session.service.js +561 -0
- package/dist/services/annotation.service.d.ts +177 -0
- package/dist/services/annotation.service.js +154 -0
- package/dist/services/artifact-notification.service.d.ts +14 -0
- package/dist/services/artifact-notification.service.js +20 -0
- package/dist/services/artifact-version.service.d.ts +38 -0
- package/dist/services/artifact-version.service.js +129 -0
- package/dist/services/artifacts/annotation.service.d.ts +177 -0
- package/dist/services/artifacts/annotation.service.js +154 -0
- package/dist/services/artifacts/artifact-version.service.d.ts +38 -0
- package/dist/services/artifacts/artifact-version.service.js +129 -0
- package/dist/services/artifacts/chat.service.d.ts +127 -0
- package/dist/services/artifacts/chat.service.js +182 -0
- package/dist/services/artifacts/study-guide.service.d.ts +18 -0
- package/dist/services/artifacts/study-guide.service.js +65 -0
- package/dist/services/auth/auth.service.d.ts +94 -0
- package/dist/services/auth/auth.service.js +368 -0
- package/dist/services/auth.service.d.ts +94 -0
- package/dist/services/auth.service.js +368 -0
- package/dist/services/base.service.d.ts +14 -0
- package/dist/services/base.service.js +14 -0
- package/dist/services/billing/payment.service.d.ts +55 -0
- package/dist/services/billing/payment.service.js +368 -0
- package/dist/services/billing/subscription.service.d.ts +37 -0
- package/dist/services/billing/subscription.service.js +654 -0
- package/dist/services/billing/usage.service.d.ts +27 -0
- package/dist/services/billing/usage.service.js +77 -0
- package/dist/services/chat.service.d.ts +127 -0
- package/dist/services/chat.service.js +182 -0
- package/dist/services/content/copilot.service.d.ts +113 -0
- package/dist/services/content/copilot.service.js +453 -0
- package/dist/services/content/flashcard-progress.service.d.ts +159 -0
- package/dist/services/content/flashcard-progress.service.js +432 -0
- package/dist/services/content/flashcard.service.d.ts +140 -0
- package/dist/services/content/flashcard.service.js +326 -0
- package/dist/services/content/media-analysis.service.d.ts +23 -0
- package/dist/services/content/media-analysis.service.js +404 -0
- package/dist/services/content/podcast.service.d.ts +267 -0
- package/dist/services/content/podcast.service.js +653 -0
- package/dist/services/content/worksheet-content.service.d.ts +37 -0
- package/dist/services/content/worksheet-content.service.js +84 -0
- package/dist/services/content/worksheet-content.service.test.d.ts +1 -0
- package/dist/services/content/worksheet-content.service.test.js +69 -0
- package/dist/services/content/worksheet-generation.service.d.ts +91 -0
- package/dist/services/content/worksheet-generation.service.js +95 -0
- package/dist/services/content/worksheet-generation.service.test.d.ts +1 -0
- package/dist/services/content/worksheet-generation.service.test.js +20 -0
- package/dist/services/content/worksheet.service.d.ts +347 -0
- package/dist/services/content/worksheet.service.js +599 -0
- package/dist/services/copilot.service.d.ts +116 -0
- package/dist/services/copilot.service.js +447 -0
- package/dist/services/flashcard-progress.service.d.ts +2 -2
- package/dist/services/flashcard-progress.service.js +3 -2
- package/dist/services/flashcard.service.d.ts +140 -0
- package/dist/services/flashcard.service.js +325 -0
- package/dist/services/invitation.service.d.ts +66 -0
- package/dist/services/invitation.service.js +348 -0
- package/dist/services/knowledge/knowledge-base.service.d.ts +316 -0
- package/dist/services/knowledge/knowledge-base.service.js +544 -0
- package/dist/services/knowledge-base.service.d.ts +316 -0
- package/dist/services/knowledge-base.service.js +536 -0
- package/dist/services/media-analysis.service.d.ts +23 -0
- package/dist/services/media-analysis.service.js +384 -0
- package/dist/services/member.service.d.ts +36 -0
- package/dist/services/member.service.js +193 -0
- package/dist/services/members/invitation.service.d.ts +66 -0
- package/dist/services/members/invitation.service.js +348 -0
- package/dist/services/members/member.service.d.ts +36 -0
- package/dist/services/members/member.service.js +193 -0
- package/dist/services/note.service.d.ts +55 -0
- package/dist/services/note.service.js +111 -0
- package/dist/services/notification.service.d.ts +214 -0
- package/dist/services/notification.service.js +550 -0
- package/dist/services/notification.service.test.d.ts +1 -0
- package/dist/services/notification.service.test.js +87 -0
- package/dist/services/notifications/notification.service.d.ts +214 -0
- package/dist/services/notifications/notification.service.js +550 -0
- package/dist/services/notifications/notification.service.test.d.ts +1 -0
- package/dist/services/notifications/notification.service.test.js +87 -0
- package/dist/services/payment.service.d.ts +55 -0
- package/dist/services/payment.service.js +368 -0
- package/dist/services/podcast.service.d.ts +267 -0
- package/dist/services/podcast.service.js +654 -0
- package/dist/services/router-module.service.d.ts +7 -0
- package/dist/services/router-module.service.js +10 -0
- package/dist/services/study-guide.service.d.ts +18 -0
- package/dist/services/study-guide.service.js +65 -0
- package/dist/services/subscription.service.d.ts +37 -0
- package/dist/services/subscription.service.js +654 -0
- package/dist/services/usage-limit-policy.service.d.ts +12 -0
- package/dist/services/usage-limit-policy.service.js +22 -0
- package/dist/services/usage-limit-policy.service.test.d.ts +1 -0
- package/dist/services/usage-limit-policy.service.test.js +46 -0
- package/dist/services/usage.service.d.ts +27 -0
- package/dist/services/usage.service.js +77 -0
- package/dist/services/worksheet-content.service.d.ts +42 -0
- package/dist/services/worksheet-content.service.js +84 -0
- package/dist/services/worksheet-content.service.test.d.ts +1 -0
- package/dist/services/worksheet-content.service.test.js +69 -0
- package/dist/services/worksheet-generation.service.d.ts +91 -0
- package/dist/services/worksheet-generation.service.js +95 -0
- package/dist/services/worksheet-generation.service.test.d.ts +1 -0
- package/dist/services/worksheet-generation.service.test.js +20 -0
- package/dist/services/worksheet.service.d.ts +385 -0
- package/dist/services/worksheet.service.js +596 -0
- package/dist/services/workspace/workspace-analytics.service.d.ts +24 -0
- package/dist/services/workspace/workspace-analytics.service.js +95 -0
- package/dist/services/workspace/workspace-kb.service.d.ts +40 -0
- package/dist/services/workspace/workspace-kb.service.js +184 -0
- package/dist/services/workspace/workspace.service.d.ts +307 -0
- package/dist/services/workspace/workspace.service.js +394 -0
- package/dist/services/workspace-analytics.service.d.ts +24 -0
- package/dist/services/workspace-analytics.service.js +95 -0
- package/dist/services/workspace-kb.service.d.ts +40 -0
- package/dist/services/workspace-kb.service.js +184 -0
- package/dist/services/workspace-progress.service.d.ts +27 -0
- package/dist/services/workspace-progress.service.js +56 -0
- package/dist/services/workspace-progress.service.test.d.ts +1 -0
- package/dist/services/workspace-progress.service.test.js +49 -0
- package/dist/services/workspace.service.d.ts +307 -0
- package/dist/services/workspace.service.js +390 -0
- package/dist/trpc.js +2 -2
- package/package.json +5 -6
- package/prisma/migrations/20260509000001_add_knowledge_base/migration.sql +99 -0
- package/prisma/migrations/20260509000002_curate_knowledge_base/migration.sql +52 -0
- package/prisma/migrations/20260522000000_add_notes/migration.sql +27 -0
- package/prisma/migrations/20260524000000_remove_notes/migration.sql +3 -0
- package/prisma/schema.prisma +150 -48
- package/prisma/seed.mjs +67 -0
- package/scripts/debug/README.md +4 -0
- package/src/README.md +63 -0
- package/src/lib/ai/config.ts +34 -0
- package/src/lib/ai/embedding-client.ts +47 -0
- package/src/lib/ai/index.ts +62 -0
- package/src/lib/ai/inference-backend/client.ts +479 -0
- package/src/lib/ai/inference-backend/mocks.ts +171 -0
- package/src/lib/ai/inference-backend/types.ts +50 -0
- package/src/lib/ai/json-parse.ts +35 -0
- package/src/lib/ai/llm-client.ts +31 -0
- package/src/lib/ai/mock.ts +12 -0
- package/src/lib/ai/types.ts +11 -0
- package/src/lib/chunking.ts +81 -0
- package/src/lib/curated-kb-seed.ts +164 -0
- package/src/lib/email.ts +77 -115
- package/src/lib/embeddings.ts +9 -0
- package/src/lib/ensure-curated-kb-catalog.ts +60 -0
- package/src/lib/env.ts +2 -7
- package/src/lib/inference.ts +1 -21
- package/src/lib/kb-meta.ts +81 -0
- package/src/lib/pdf.ts +23 -0
- package/src/lib/workspace-kb.ts +7 -0
- package/src/repositories/artifact.repository.ts +55 -0
- package/src/repositories/base.repository.ts +19 -0
- package/src/repositories/invitation.repository.ts +53 -0
- package/src/repositories/notification.repository.ts +53 -0
- package/src/repositories/user.repository.ts +44 -0
- package/src/repositories/workspace-member.repository.ts +38 -0
- package/src/repositories/workspace.repository.ts +89 -0
- package/src/routers/_app.ts +4 -0
- package/src/routers/admin.ts +124 -692
- package/src/routers/annotations.ts +25 -203
- package/src/routers/artifactVersions.ts +32 -0
- package/src/routers/auth.ts +81 -519
- package/src/routers/chat.ts +42 -245
- package/src/routers/copilot.ts +41 -666
- package/src/routers/flashcards.ts +108 -404
- package/src/routers/knowledgeBase.ts +216 -0
- package/src/routers/members.ts +60 -782
- package/src/routers/notifications.ts +15 -117
- package/src/routers/payment.ts +37 -446
- package/src/routers/podcast.ts +36 -898
- package/src/routers/studyguide.ts +5 -144
- package/src/routers/worksheets.ts +171 -735
- package/src/routers/workspace.ts +138 -1109
- package/src/scripts/purge-deleted-users.ts +2 -2
- package/src/server.ts +10 -3
- package/src/{lib/activity_human_description.test.ts → services/activity/activity-human-description.service.test.ts} +1 -1
- package/src/{lib/activity_log_service.test.ts → services/activity/activity-log.service.test.ts} +1 -1
- package/src/{lib/activity_log_service.ts → services/activity/activity-log.service.ts} +2 -2
- package/src/services/admin/admin.service.ts +612 -0
- package/src/services/ai/ai-session.service.ts +5 -0
- package/src/services/artifacts/annotation.service.ts +189 -0
- package/src/services/artifacts/artifact-version.service.ts +151 -0
- package/src/services/artifacts/chat.service.ts +197 -0
- package/src/services/artifacts/study-guide.service.ts +72 -0
- package/src/services/auth/auth.service.ts +473 -0
- package/src/services/base.service.ts +19 -0
- package/src/services/billing/payment.service.ts +436 -0
- package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
- package/src/{lib/usage_service.ts → services/billing/usage.service.ts} +32 -12
- package/src/services/content/copilot.service.ts +596 -0
- package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +6 -3
- package/src/services/content/flashcard.service.ts +394 -0
- package/src/services/content/media-analysis.service.ts +556 -0
- package/src/services/content/podcast.service.ts +777 -0
- package/src/services/content/worksheet-content.service.test.ts +83 -0
- package/src/services/content/worksheet-content.service.ts +117 -0
- package/src/{lib/worksheet-generation.test.ts → services/content/worksheet-generation.service.test.ts} +1 -1
- package/src/services/content/worksheet.service.ts +751 -0
- package/src/services/knowledge/knowledge-base.service.ts +705 -0
- package/src/services/members/invitation.service.ts +427 -0
- package/src/services/members/member.service.ts +241 -0
- package/src/{lib/notification-service.test.ts → services/notifications/notification.service.test.ts} +2 -2
- package/src/{lib/notification-service.ts → services/notifications/notification.service.ts} +102 -1
- package/src/services/workspace/workspace-analytics.service.ts +107 -0
- package/src/services/workspace/workspace-kb.service.ts +273 -0
- package/src/services/workspace/workspace.service.ts +481 -0
- package/src/trpc.ts +2 -2
- package/src/lib/ai-session.ts +0 -704
- package/src/lib/workspace-access.ts +0 -13
- /package/{check-difficulty.cjs → scripts/debug/check-difficulty.cjs} +0 -0
- /package/{check-questions.cjs → scripts/debug/check-questions.cjs} +0 -0
- /package/{db-summary.cjs → scripts/debug/db-summary.cjs} +0 -0
- /package/{mcq-test.cjs → scripts/debug/mcq-test.cjs} +0 -0
- /package/{test-generate.js → scripts/debug/test-generate.js} +0 -0
- /package/{test-ratio.cjs → scripts/debug/test-ratio.cjs} +0 -0
- /package/{zod-test.cjs → scripts/debug/zod-test.cjs} +0 -0
- /package/src/{lib/activity_human_description.ts → services/activity/activity-human-description.service.ts} +0 -0
- /package/src/{lib/worksheet-generation.ts → services/content/worksheet-generation.service.ts} +0 -0
package/dist/routers/auth.js
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { router, publicProcedure, authedProcedure } from '../trpc.js';
|
|
3
|
-
import PusherService from '../lib/pusher.js';
|
|
4
|
-
import { logger } from '../lib/logger.js';
|
|
5
|
-
import bcrypt from 'bcryptjs';
|
|
6
3
|
import { serialize } from 'cookie';
|
|
7
|
-
import crypto from 'node:crypto';
|
|
8
4
|
import { TRPCError } from '@trpc/server';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
5
|
+
import PusherService from '../lib/pusher.js';
|
|
6
|
+
import { logger } from '../lib/logger.js';
|
|
7
|
+
import { AuthService } from '../services/auth/auth.service.js';
|
|
8
|
+
/**
|
|
9
|
+
* Router owns HTTP-level concerns (cookie set/clear). The service is
|
|
10
|
+
* HTTP-free and returns pure data; the router wires it into `ctx.res`.
|
|
11
|
+
*/
|
|
13
12
|
function getAuthCookieConfig(isProduction) {
|
|
14
13
|
return {
|
|
15
14
|
httpOnly: true,
|
|
@@ -17,129 +16,27 @@ function getAuthCookieConfig(isProduction) {
|
|
|
17
16
|
sameSite: (isProduction ? 'none' : 'lax'),
|
|
18
17
|
path: '/',
|
|
19
18
|
maxAge: 60 * 60 * 24 * 30,
|
|
20
|
-
// Use parent domain in production so scribe.study and api.scribe.study share auth state.
|
|
21
19
|
domain: isProduction ? '.scribe.study' : undefined,
|
|
22
20
|
};
|
|
23
21
|
}
|
|
24
|
-
// Helper to create custom auth token
|
|
25
22
|
const passwordFieldSchema = z
|
|
26
23
|
.string()
|
|
27
24
|
.min(8, 'Password must be at least 8 characters')
|
|
28
25
|
.regex(/[A-Z]/, 'Password must contain at least one uppercase letter')
|
|
29
26
|
.regex(/[0-9]/, 'Password must contain at least one number')
|
|
30
27
|
.regex(/[^a-zA-Z0-9]/, 'Password must contain at least one special character');
|
|
31
|
-
function hashPasswordResetToken(rawToken) {
|
|
32
|
-
return crypto.createHash('sha256').update(rawToken, 'utf8').digest('hex');
|
|
33
|
-
}
|
|
34
|
-
/** Use until `npx prisma generate` runs after adding PasswordResetToken to the schema. */
|
|
35
|
-
function passwordResetDb(ctx) {
|
|
36
|
-
return ctx.db.passwordResetToken;
|
|
37
|
-
}
|
|
38
|
-
function createCustomAuthToken(userId) {
|
|
39
|
-
const secret = process.env.AUTH_SECRET;
|
|
40
|
-
if (!secret) {
|
|
41
|
-
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: "AUTH_SECRET is not set" });
|
|
42
|
-
}
|
|
43
|
-
const base64UserId = Buffer.from(userId, 'utf8').toString('base64url');
|
|
44
|
-
const hmac = crypto.createHmac('sha256', secret);
|
|
45
|
-
hmac.update(base64UserId);
|
|
46
|
-
const signature = hmac.digest('hex');
|
|
47
|
-
return `${base64UserId}.${signature}`;
|
|
48
|
-
}
|
|
49
28
|
export const auth = router({
|
|
50
29
|
updateProfile: publicProcedure
|
|
51
|
-
.input(z.object({
|
|
52
|
-
|
|
53
|
-
}))
|
|
54
|
-
.mutation(async ({ ctx, input }) => {
|
|
55
|
-
const { name } = input;
|
|
56
|
-
await ctx.db.user.update({
|
|
57
|
-
where: {
|
|
58
|
-
id: ctx.session.user.id,
|
|
59
|
-
},
|
|
60
|
-
data: {
|
|
61
|
-
name: name,
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
return {
|
|
65
|
-
success: true,
|
|
66
|
-
message: 'Profile updated successfully',
|
|
67
|
-
};
|
|
68
|
-
}),
|
|
30
|
+
.input(z.object({ name: z.string().min(1) }))
|
|
31
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).updateProfile(ctx.session.user.id, input)),
|
|
69
32
|
changePassword: authedProcedure
|
|
70
33
|
.input(z.object({
|
|
71
|
-
currentPassword: z.string().min(1,
|
|
34
|
+
currentPassword: z.string().min(1, 'Current password is required'),
|
|
72
35
|
newPassword: passwordFieldSchema,
|
|
73
36
|
}))
|
|
74
|
-
.mutation(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
select: { id: true, passwordHash: true },
|
|
78
|
-
});
|
|
79
|
-
if (!user) {
|
|
80
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
81
|
-
}
|
|
82
|
-
if (!user.passwordHash) {
|
|
83
|
-
throw new TRPCError({
|
|
84
|
-
code: 'BAD_REQUEST',
|
|
85
|
-
message: 'Password change is unavailable for this account.',
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
const validCurrentPassword = await bcrypt.compare(input.currentPassword, user.passwordHash);
|
|
89
|
-
if (!validCurrentPassword) {
|
|
90
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Current password is incorrect' });
|
|
91
|
-
}
|
|
92
|
-
const isSamePassword = await bcrypt.compare(input.newPassword, user.passwordHash);
|
|
93
|
-
if (isSamePassword) {
|
|
94
|
-
throw new TRPCError({
|
|
95
|
-
code: 'BAD_REQUEST',
|
|
96
|
-
message: 'New password must be different from current password',
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
const newHash = await bcrypt.hash(input.newPassword, 10);
|
|
100
|
-
await ctx.db.user.update({
|
|
101
|
-
where: { id: user.id },
|
|
102
|
-
data: { passwordHash: newHash },
|
|
103
|
-
});
|
|
104
|
-
return { success: true, message: 'Password changed successfully' };
|
|
105
|
-
}),
|
|
106
|
-
uploadProfilePicture: authedProcedure
|
|
107
|
-
.mutation(async ({ ctx }) => {
|
|
108
|
-
const userId = ctx.session.user.id;
|
|
109
|
-
logger.info(`Generating upload URL for user ${userId}`, 'AUTH');
|
|
110
|
-
const objectKey = `profile_picture_${userId}`;
|
|
111
|
-
const { data: signedUrlData, error: signedUrlError } = await supabaseClient.storage
|
|
112
|
-
.from('media')
|
|
113
|
-
.createSignedUploadUrl(objectKey, { upsert: true });
|
|
114
|
-
if (signedUrlError) {
|
|
115
|
-
logger.error(`Failed to generate upload URL: ${signedUrlError.message}`, 'AUTH');
|
|
116
|
-
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: `Failed to generate upload URL: ${signedUrlError.message}` });
|
|
117
|
-
}
|
|
118
|
-
const fileAsset = await ctx.db.fileAsset.create({
|
|
119
|
-
data: {
|
|
120
|
-
userId: userId,
|
|
121
|
-
name: 'Profile Picture',
|
|
122
|
-
mimeType: 'image/jpeg',
|
|
123
|
-
size: 0,
|
|
124
|
-
bucket: 'media',
|
|
125
|
-
objectKey: objectKey,
|
|
126
|
-
},
|
|
127
|
-
});
|
|
128
|
-
await ctx.db.user.update({
|
|
129
|
-
where: { id: userId },
|
|
130
|
-
data: {
|
|
131
|
-
fileAssetId: fileAsset.id,
|
|
132
|
-
},
|
|
133
|
-
});
|
|
134
|
-
logger.info(`Profile picture asset created and linked for user ${userId}`, 'AUTH');
|
|
135
|
-
return {
|
|
136
|
-
success: true,
|
|
137
|
-
message: 'Profile picture uploaded successfully',
|
|
138
|
-
signedUrl: signedUrlData.signedUrl,
|
|
139
|
-
};
|
|
140
|
-
}),
|
|
141
|
-
confirmProfileUpdate: authedProcedure
|
|
142
|
-
.mutation(async ({ ctx }) => {
|
|
37
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).changePassword(ctx.session.user.id, input)),
|
|
38
|
+
uploadProfilePicture: authedProcedure.mutation(({ ctx }) => new AuthService(ctx.db).uploadProfilePicture(ctx.session.user.id)),
|
|
39
|
+
confirmProfileUpdate: authedProcedure.mutation(async ({ ctx }) => {
|
|
143
40
|
logger.info(`Confirming profile update for user ${ctx.session.user.id}`, 'AUTH');
|
|
144
41
|
await PusherService.emitProfileUpdate(ctx.session.user.id);
|
|
145
42
|
return { success: true };
|
|
@@ -150,333 +47,51 @@ export const auth = router({
|
|
|
150
47
|
email: z.string().email(),
|
|
151
48
|
password: passwordFieldSchema,
|
|
152
49
|
}))
|
|
153
|
-
.mutation(
|
|
154
|
-
const existing = await ctx.db.user.findUnique({
|
|
155
|
-
where: { email: input.email },
|
|
156
|
-
});
|
|
157
|
-
if (existing) {
|
|
158
|
-
throw new TRPCError({ code: 'CONFLICT', message: "Email already registered" });
|
|
159
|
-
}
|
|
160
|
-
const hash = await bcrypt.hash(input.password, 10);
|
|
161
|
-
// Get default "User" role
|
|
162
|
-
const userRole = await ctx.db.role.findUnique({
|
|
163
|
-
where: { name: 'User' },
|
|
164
|
-
});
|
|
165
|
-
const user = await ctx.db.user.create({
|
|
166
|
-
data: {
|
|
167
|
-
name: input.name,
|
|
168
|
-
email: input.email,
|
|
169
|
-
passwordHash: hash,
|
|
170
|
-
roleId: userRole?.id,
|
|
171
|
-
// emailVerified is null -- user must verify
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
await notifyAdminsOnSignup(ctx.db, {
|
|
175
|
-
id: user.id,
|
|
176
|
-
name: user.name,
|
|
177
|
-
email: user.email,
|
|
178
|
-
});
|
|
179
|
-
// Create verification token (24h expiry)
|
|
180
|
-
const token = crypto.randomUUID();
|
|
181
|
-
await ctx.db.verificationToken.create({
|
|
182
|
-
data: {
|
|
183
|
-
identifier: input.email,
|
|
184
|
-
token,
|
|
185
|
-
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
// Send verification email (non-blocking)
|
|
189
|
-
sendVerificationEmail(input.email, token, input.name).catch(() => { });
|
|
190
|
-
// Create Stripe Customer (non-blocking for registration, but we want it)
|
|
191
|
-
createStripeCustomer(input.email, input.name).then(async (stripeCustomerId) => {
|
|
192
|
-
if (stripeCustomerId) {
|
|
193
|
-
await ctx.db.user.update({
|
|
194
|
-
where: { id: user.id },
|
|
195
|
-
data: { stripe_customer_id: stripeCustomerId }
|
|
196
|
-
}).catch((err) => logger.error(`Failed to update user with stripe_customer_id: ${err.message}`, 'AUTH'));
|
|
197
|
-
}
|
|
198
|
-
});
|
|
199
|
-
return { id: user.id, email: user.email, name: user.name };
|
|
200
|
-
}),
|
|
201
|
-
// Verify email with token from the email link
|
|
50
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).signup(input)),
|
|
202
51
|
verifyEmail: publicProcedure
|
|
203
|
-
.input(z.object({
|
|
204
|
-
|
|
205
|
-
}))
|
|
206
|
-
.mutation(async ({ ctx, input }) => {
|
|
207
|
-
const record = await ctx.db.verificationToken.findUnique({
|
|
208
|
-
where: { token: input.token },
|
|
209
|
-
});
|
|
210
|
-
if (!record) {
|
|
211
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'Invalid or expired verification link' });
|
|
212
|
-
}
|
|
213
|
-
if (record.expires < new Date()) {
|
|
214
|
-
// Clean up expired token
|
|
215
|
-
await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
|
|
216
|
-
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Verification link has expired. Please request a new one.' });
|
|
217
|
-
}
|
|
218
|
-
// Mark user as verified
|
|
219
|
-
await ctx.db.user.update({
|
|
220
|
-
where: { email: record.identifier },
|
|
221
|
-
data: { emailVerified: new Date() },
|
|
222
|
-
});
|
|
223
|
-
// Delete used token
|
|
224
|
-
await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
|
|
225
|
-
return { success: true, message: 'Email verified successfully' };
|
|
226
|
-
}),
|
|
227
|
-
// Resend verification email (for logged-in users who haven't verified)
|
|
228
|
-
resendVerification: publicProcedure
|
|
229
|
-
.mutation(async ({ ctx }) => {
|
|
230
|
-
if (!ctx.session?.user?.id) {
|
|
231
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not logged in' });
|
|
232
|
-
}
|
|
233
|
-
const user = await ctx.db.user.findUnique({
|
|
234
|
-
where: { id: ctx.session.user.id },
|
|
235
|
-
});
|
|
236
|
-
if (!user || !user.email) {
|
|
237
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
238
|
-
}
|
|
239
|
-
if (user.emailVerified) {
|
|
240
|
-
return { success: true, message: 'Email is already verified' };
|
|
241
|
-
}
|
|
242
|
-
// Delete any existing tokens for this email
|
|
243
|
-
await ctx.db.verificationToken.deleteMany({
|
|
244
|
-
where: { identifier: user.email },
|
|
245
|
-
});
|
|
246
|
-
// Create new token
|
|
247
|
-
const token = crypto.randomUUID();
|
|
248
|
-
await ctx.db.verificationToken.create({
|
|
249
|
-
data: {
|
|
250
|
-
identifier: user.email,
|
|
251
|
-
token,
|
|
252
|
-
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
const sent = await sendVerificationEmail(user.email, token, user.name);
|
|
256
|
-
if (!sent) {
|
|
257
|
-
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to send email. Please try again.' });
|
|
258
|
-
}
|
|
259
|
-
return { success: true, message: 'Verification email sent' };
|
|
260
|
-
}),
|
|
52
|
+
.input(z.object({ token: z.string() }))
|
|
53
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).verifyEmail(input.token)),
|
|
54
|
+
resendVerification: publicProcedure.mutation(({ ctx }) => new AuthService(ctx.db).resendVerification(ctx.session?.user?.id)),
|
|
261
55
|
login: publicProcedure
|
|
262
56
|
.input(z.object({
|
|
263
57
|
email: z.string().email(),
|
|
264
58
|
password: z.string().min(6),
|
|
265
59
|
}))
|
|
266
60
|
.mutation(async ({ ctx, input }) => {
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
if (user.deletedAt) {
|
|
274
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: "Account scheduled for deletion. Please check your email for a restore link." });
|
|
275
|
-
}
|
|
276
|
-
const valid = await bcrypt.compare(input.password, user.passwordHash);
|
|
277
|
-
if (!valid) {
|
|
278
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: "Invalid credentials" });
|
|
279
|
-
}
|
|
280
|
-
// Create custom auth token
|
|
281
|
-
const authToken = createCustomAuthToken(user.id);
|
|
282
|
-
const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
|
|
283
|
-
const cookieValue = serialize("auth_token", authToken, getAuthCookieConfig(isProduction));
|
|
284
|
-
ctx.res.setHeader("Set-Cookie", cookieValue);
|
|
285
|
-
return {
|
|
286
|
-
id: user.id,
|
|
287
|
-
email: user.email,
|
|
288
|
-
name: user.name,
|
|
289
|
-
token: authToken
|
|
290
|
-
};
|
|
61
|
+
const result = await new AuthService(ctx.db).login(input);
|
|
62
|
+
const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER);
|
|
63
|
+
const cookieValue = serialize('auth_token', result.token, getAuthCookieConfig(isProduction));
|
|
64
|
+
ctx.res.setHeader('Set-Cookie', cookieValue);
|
|
65
|
+
return result;
|
|
291
66
|
}),
|
|
292
|
-
/**
|
|
293
|
-
* Request a password reset email. Always returns the same message (no email enumeration).
|
|
294
|
-
*/
|
|
295
67
|
requestPasswordReset: publicProcedure
|
|
296
68
|
.input(z.object({ email: z.string().email() }))
|
|
297
|
-
.mutation(
|
|
298
|
-
const email = input.email.trim().toLowerCase();
|
|
299
|
-
const generic = {
|
|
300
|
-
success: true,
|
|
301
|
-
message: 'If an account exists for this email, we sent password reset instructions.',
|
|
302
|
-
};
|
|
303
|
-
const user = await ctx.db.user.findUnique({
|
|
304
|
-
where: { email },
|
|
305
|
-
select: {
|
|
306
|
-
id: true,
|
|
307
|
-
email: true,
|
|
308
|
-
name: true,
|
|
309
|
-
passwordHash: true,
|
|
310
|
-
deletedAt: true,
|
|
311
|
-
},
|
|
312
|
-
});
|
|
313
|
-
if (!user?.passwordHash || user.deletedAt || !user.email) {
|
|
314
|
-
return generic;
|
|
315
|
-
}
|
|
316
|
-
await passwordResetDb(ctx).deleteMany({
|
|
317
|
-
where: { userId: user.id, usedAt: null },
|
|
318
|
-
});
|
|
319
|
-
const rawToken = crypto.randomBytes(32).toString('hex');
|
|
320
|
-
const tokenHash = hashPasswordResetToken(rawToken);
|
|
321
|
-
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
|
|
322
|
-
await passwordResetDb(ctx).create({
|
|
323
|
-
data: {
|
|
324
|
-
userId: user.id,
|
|
325
|
-
tokenHash,
|
|
326
|
-
expiresAt,
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
sendPasswordResetEmail(user.email, rawToken, user.name).catch(() => { });
|
|
330
|
-
return generic;
|
|
331
|
-
}),
|
|
332
|
-
/**
|
|
333
|
-
* Complete password reset using the token from the email link.
|
|
334
|
-
*/
|
|
69
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).requestPasswordReset(input)),
|
|
335
70
|
resetPassword: publicProcedure
|
|
336
71
|
.input(z.object({
|
|
337
72
|
token: z.string().min(1),
|
|
338
73
|
newPassword: passwordFieldSchema,
|
|
339
74
|
}))
|
|
340
|
-
.mutation(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (!record || record.usedAt || record.expiresAt < new Date()) {
|
|
346
|
-
throw new TRPCError({
|
|
347
|
-
code: 'BAD_REQUEST',
|
|
348
|
-
message: 'Invalid or expired reset link. Please request a new one.',
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
const newHash = await bcrypt.hash(input.newPassword, 10);
|
|
352
|
-
await ctx.db.user.update({
|
|
353
|
-
where: { id: record.userId },
|
|
354
|
-
data: { passwordHash: newHash },
|
|
355
|
-
});
|
|
356
|
-
await passwordResetDb(ctx).update({
|
|
357
|
-
where: { id: record.id },
|
|
358
|
-
data: { usedAt: new Date() },
|
|
359
|
-
});
|
|
360
|
-
await passwordResetDb(ctx).deleteMany({
|
|
361
|
-
where: { userId: record.userId, id: { not: record.id } },
|
|
362
|
-
});
|
|
363
|
-
return { success: true, message: 'Password updated. You can sign in now.' };
|
|
364
|
-
}),
|
|
365
|
-
getSession: publicProcedure.query(async ({ ctx }) => {
|
|
366
|
-
// Just return the current session from context
|
|
367
|
-
if (!ctx.session) {
|
|
368
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: "No session found" });
|
|
369
|
-
}
|
|
370
|
-
const userId = ctx.session.user.id;
|
|
371
|
-
const user = await ctx.db.user.findUnique({
|
|
372
|
-
where: { id: userId },
|
|
373
|
-
include: {
|
|
374
|
-
profilePicture: true,
|
|
375
|
-
role: true,
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
if (!user) {
|
|
379
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: "User not found" });
|
|
380
|
-
}
|
|
381
|
-
const profilePictureUrl = user.profilePicture?.objectKey
|
|
382
|
-
? `/profile-picture/${user.profilePicture.objectKey}?t=${new Date(user.updatedAt).getTime()}`
|
|
383
|
-
: null;
|
|
384
|
-
logger.info(`Session fetched for user ${userId}, profilePicture: ${profilePictureUrl}`, 'AUTH');
|
|
385
|
-
return {
|
|
386
|
-
user: {
|
|
387
|
-
id: user.id,
|
|
388
|
-
email: user.email,
|
|
389
|
-
name: user.name,
|
|
390
|
-
emailVerified: !!user.emailVerified,
|
|
391
|
-
profilePicture: profilePictureUrl,
|
|
392
|
-
role: user.role,
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
}),
|
|
396
|
-
requestAccountDeletion: authedProcedure
|
|
397
|
-
.mutation(async ({ ctx }) => {
|
|
398
|
-
const user = await ctx.db.user.findUnique({
|
|
399
|
-
where: { id: ctx.session.user.id },
|
|
400
|
-
});
|
|
401
|
-
if (!user) {
|
|
402
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
403
|
-
}
|
|
404
|
-
// Mark user as deleted
|
|
405
|
-
await ctx.db.user.update({
|
|
406
|
-
where: { id: user.id },
|
|
407
|
-
data: { deletedAt: new Date() },
|
|
408
|
-
});
|
|
409
|
-
await notifyAdminsAccountDeletionScheduled(ctx.db, {
|
|
410
|
-
id: user.id,
|
|
411
|
-
name: user.name,
|
|
412
|
-
email: user.email,
|
|
413
|
-
}).catch(() => { });
|
|
414
|
-
// Clear existing restore tokens
|
|
415
|
-
await ctx.db.verificationToken.deleteMany({
|
|
416
|
-
where: { identifier: `restore-${user.email}` },
|
|
417
|
-
});
|
|
418
|
-
// Create restore token (30 days expiry)
|
|
419
|
-
const token = crypto.randomUUID();
|
|
420
|
-
await ctx.db.verificationToken.create({
|
|
421
|
-
data: {
|
|
422
|
-
identifier: `restore-${user.email}`,
|
|
423
|
-
token,
|
|
424
|
-
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
425
|
-
},
|
|
426
|
-
});
|
|
427
|
-
// Send email
|
|
428
|
-
if (user.email) {
|
|
429
|
-
sendAccountDeletionScheduledEmail(user.email, token).catch(() => { });
|
|
430
|
-
}
|
|
431
|
-
// Log out user by clearing cookie
|
|
432
|
-
const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
|
|
75
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).resetPassword(input)),
|
|
76
|
+
getSession: publicProcedure.query(({ ctx }) => new AuthService(ctx.db).getSession(ctx.session?.user?.id)),
|
|
77
|
+
requestAccountDeletion: authedProcedure.mutation(async ({ ctx }) => {
|
|
78
|
+
const result = await new AuthService(ctx.db).requestAccountDeletion(ctx.session.user.id);
|
|
79
|
+
const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER);
|
|
433
80
|
const clearCookieConfig = getAuthCookieConfig(isProduction);
|
|
434
|
-
ctx.res.setHeader(
|
|
435
|
-
|
|
436
|
-
maxAge: 0,
|
|
437
|
-
}));
|
|
438
|
-
return { success: true, message: 'Account scheduled for deletion' };
|
|
81
|
+
ctx.res.setHeader('Set-Cookie', serialize('auth_token', '', { ...clearCookieConfig, maxAge: 0 }));
|
|
82
|
+
return result;
|
|
439
83
|
}),
|
|
440
84
|
restoreAccount: publicProcedure
|
|
441
|
-
.input(z.object({
|
|
442
|
-
|
|
443
|
-
}))
|
|
444
|
-
.mutation(async ({ ctx, input }) => {
|
|
445
|
-
const record = await ctx.db.verificationToken.findUnique({
|
|
446
|
-
where: { token: input.token },
|
|
447
|
-
});
|
|
448
|
-
if (!record || !record.identifier.startsWith('restore-')) {
|
|
449
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'Invalid or expired restore link' });
|
|
450
|
-
}
|
|
451
|
-
if (record.expires < new Date()) {
|
|
452
|
-
await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
|
|
453
|
-
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Restore link has expired.' });
|
|
454
|
-
}
|
|
455
|
-
const email = record.identifier.replace('restore-', '');
|
|
456
|
-
// Mark user as restored
|
|
457
|
-
await ctx.db.user.update({
|
|
458
|
-
where: { email },
|
|
459
|
-
data: { deletedAt: null },
|
|
460
|
-
});
|
|
461
|
-
// Delete used token
|
|
462
|
-
await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
|
|
463
|
-
// Send confirmation email
|
|
464
|
-
sendAccountRestoredEmail(email).catch(() => { });
|
|
465
|
-
return { success: true, message: 'Account restored successfully' };
|
|
466
|
-
}),
|
|
85
|
+
.input(z.object({ token: z.string() }))
|
|
86
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).restoreAccount(input.token)),
|
|
467
87
|
logout: publicProcedure.mutation(async ({ ctx }) => {
|
|
468
|
-
const token = ctx.cookies[
|
|
88
|
+
const token = ctx.cookies['auth_token'];
|
|
469
89
|
if (!token) {
|
|
470
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message:
|
|
90
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'No token found' });
|
|
471
91
|
}
|
|
472
|
-
|
|
473
|
-
// custom HMAC auth system (auth_token cookie).
|
|
474
|
-
const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER);
|
|
92
|
+
const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER);
|
|
475
93
|
const clearCookieConfig = getAuthCookieConfig(isProduction);
|
|
476
|
-
ctx.res.setHeader(
|
|
477
|
-
...clearCookieConfig,
|
|
478
|
-
maxAge: 0, // Expire immediately
|
|
479
|
-
}));
|
|
94
|
+
ctx.res.setHeader('Set-Cookie', serialize('auth_token', '', { ...clearCookieConfig, maxAge: 0 }));
|
|
480
95
|
return { success: true };
|
|
481
96
|
}),
|
|
482
97
|
});
|