@goscribe/server 1.3.4 → 1.6.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/context.d.ts +14 -1
- package/dist/context.js +23 -2
- 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 +48 -0
- package/dist/lib/ai/index.js +29 -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 +7 -0
- package/dist/lib/ai/llm-client.js +36 -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 +566 -111
- 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 +37 -422
- 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 +17 -6
- 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 +2 -12
- 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 +27 -71
- package/dist/routers/workspace.js +29 -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 +44 -0
- package/dist/services/billing/payment.service.js +365 -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 +47 -0
- package/dist/services/billing/usage.service.js +149 -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 +439 -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 +184 -0
- package/dist/services/content/flashcard.service.js +339 -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 +263 -0
- package/dist/services/workspace/workspace.service.js +401 -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.d.ts +12 -4
- package/dist/trpc.js +7 -13
- 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/context.ts +33 -3
- package/src/lib/ai/config.ts +34 -0
- package/src/lib/ai/embedding-client.ts +47 -0
- package/src/lib/ai/index.ts +65 -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 +54 -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 +82 -520
- 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 +141 -1108
- 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 +433 -0
- package/src/{lib/subscription_service.ts → services/billing/subscription.service.ts} +5 -5
- package/src/services/billing/usage.service.ts +207 -0
- package/src/services/content/copilot.service.ts +587 -0
- package/src/services/{flashcard-progress.service.ts → content/flashcard-progress.service.ts} +18 -12
- package/src/services/content/flashcard.service.ts +417 -0
- package/src/services/content/media-analysis.service.ts +561 -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} +3 -3
- 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 +488 -0
- package/src/trpc.ts +7 -15
- package/src/lib/ai-session.ts +0 -704
- package/src/lib/usage_service.ts +0 -74
- 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/src/routers/auth.ts
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import {
|
|
18
|
-
notifyAdminsAccountDeletionScheduled,
|
|
19
|
-
notifyAdminsOnSignup,
|
|
20
|
-
} from '../lib/notification-service.js';
|
|
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
|
+
/**
|
|
10
|
+
* Router owns HTTP-level concerns (cookie set/clear). The service is
|
|
11
|
+
* HTTP-free and returns pure data; the router wires it into `ctx.res`.
|
|
12
|
+
*/
|
|
21
13
|
|
|
22
14
|
function getAuthCookieConfig(isProduction: boolean) {
|
|
23
15
|
return {
|
|
@@ -26,12 +18,10 @@ function getAuthCookieConfig(isProduction: boolean) {
|
|
|
26
18
|
sameSite: (isProduction ? 'none' : 'lax') as 'none' | 'lax',
|
|
27
19
|
path: '/',
|
|
28
20
|
maxAge: 60 * 60 * 24 * 30,
|
|
29
|
-
// Use parent domain in production so scribe.study and api.scribe.study share auth state.
|
|
30
21
|
domain: isProduction ? '.scribe.study' : undefined,
|
|
31
22
|
};
|
|
32
23
|
}
|
|
33
24
|
|
|
34
|
-
// Helper to create custom auth token
|
|
35
25
|
const passwordFieldSchema = z
|
|
36
26
|
.string()
|
|
37
27
|
.min(8, 'Password must be at least 8 characters')
|
|
@@ -39,549 +29,121 @@ const passwordFieldSchema = z
|
|
|
39
29
|
.regex(/[0-9]/, 'Password must contain at least one number')
|
|
40
30
|
.regex(/[^a-zA-Z0-9]/, 'Password must contain at least one special character');
|
|
41
31
|
|
|
42
|
-
function hashPasswordResetToken(rawToken: string): string {
|
|
43
|
-
return crypto.createHash('sha256').update(rawToken, 'utf8').digest('hex');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Use until `npx prisma generate` runs after adding PasswordResetToken to the schema. */
|
|
47
|
-
function passwordResetDb(ctx: { db: any }) {
|
|
48
|
-
return ctx.db.passwordResetToken;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function createCustomAuthToken(userId: string): string {
|
|
52
|
-
const secret = process.env.AUTH_SECRET;
|
|
53
|
-
if (!secret) {
|
|
54
|
-
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: "AUTH_SECRET is not set" });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const base64UserId = Buffer.from(userId, 'utf8').toString('base64url');
|
|
58
|
-
const hmac = crypto.createHmac('sha256', secret);
|
|
59
|
-
hmac.update(base64UserId);
|
|
60
|
-
const signature = hmac.digest('hex');
|
|
61
|
-
return `${base64UserId}.${signature}`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
32
|
export const auth = router({
|
|
65
|
-
updateProfile:
|
|
66
|
-
.input(z.object({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
const { name } = input;
|
|
71
|
-
|
|
72
|
-
await ctx.db.user.update({
|
|
73
|
-
where: {
|
|
74
|
-
id: ctx.session.user.id,
|
|
75
|
-
},
|
|
76
|
-
data: {
|
|
77
|
-
name: name,
|
|
78
|
-
}
|
|
79
|
-
});
|
|
33
|
+
updateProfile: authedProcedure
|
|
34
|
+
.input(z.object({ name: z.string().min(1) }))
|
|
35
|
+
.mutation(({ ctx, input }) =>
|
|
36
|
+
new AuthService(ctx.db).updateProfile(ctx.session.user.id, input),
|
|
37
|
+
),
|
|
80
38
|
|
|
81
|
-
return {
|
|
82
|
-
success: true,
|
|
83
|
-
message: 'Profile updated successfully',
|
|
84
|
-
};
|
|
85
|
-
}),
|
|
86
39
|
changePassword: authedProcedure
|
|
87
|
-
.input(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!user) {
|
|
98
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (!user.passwordHash) {
|
|
102
|
-
throw new TRPCError({
|
|
103
|
-
code: 'BAD_REQUEST',
|
|
104
|
-
message: 'Password change is unavailable for this account.',
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const validCurrentPassword = await bcrypt.compare(input.currentPassword, user.passwordHash);
|
|
109
|
-
if (!validCurrentPassword) {
|
|
110
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Current password is incorrect' });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const isSamePassword = await bcrypt.compare(input.newPassword, user.passwordHash);
|
|
114
|
-
if (isSamePassword) {
|
|
115
|
-
throw new TRPCError({
|
|
116
|
-
code: 'BAD_REQUEST',
|
|
117
|
-
message: 'New password must be different from current password',
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const newHash = await bcrypt.hash(input.newPassword, 10);
|
|
122
|
-
await ctx.db.user.update({
|
|
123
|
-
where: { id: user.id },
|
|
124
|
-
data: { passwordHash: newHash },
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
return { success: true, message: 'Password changed successfully' };
|
|
128
|
-
}),
|
|
129
|
-
uploadProfilePicture: authedProcedure
|
|
130
|
-
.mutation(async ({ ctx }) => {
|
|
131
|
-
const userId = ctx.session.user.id;
|
|
132
|
-
logger.info(`Generating upload URL for user ${userId}`, 'AUTH');
|
|
133
|
-
const objectKey = `profile_picture_${userId}`;
|
|
134
|
-
const { data: signedUrlData, error: signedUrlError } = await supabaseClient.storage
|
|
135
|
-
.from('media')
|
|
136
|
-
.createSignedUploadUrl(objectKey, { upsert: true });
|
|
137
|
-
|
|
138
|
-
if (signedUrlError) {
|
|
139
|
-
logger.error(`Failed to generate upload URL: ${signedUrlError.message}`, 'AUTH');
|
|
140
|
-
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: `Failed to generate upload URL: ${signedUrlError.message}` });
|
|
141
|
-
}
|
|
40
|
+
.input(
|
|
41
|
+
z.object({
|
|
42
|
+
currentPassword: z.string().min(1, 'Current password is required'),
|
|
43
|
+
newPassword: passwordFieldSchema,
|
|
44
|
+
}),
|
|
45
|
+
)
|
|
46
|
+
.mutation(({ ctx, input }) =>
|
|
47
|
+
new AuthService(ctx.db).changePassword(ctx.session.user.id, input),
|
|
48
|
+
),
|
|
142
49
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
name: 'Profile Picture',
|
|
147
|
-
mimeType: 'image/jpeg',
|
|
148
|
-
size: 0,
|
|
149
|
-
bucket: 'media',
|
|
150
|
-
objectKey: objectKey,
|
|
151
|
-
},
|
|
152
|
-
});
|
|
50
|
+
uploadProfilePicture: authedProcedure.mutation(({ ctx }) =>
|
|
51
|
+
new AuthService(ctx.db).uploadProfilePicture(ctx.session.user.id),
|
|
52
|
+
),
|
|
153
53
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
});
|
|
54
|
+
confirmProfileUpdate: authedProcedure.mutation(async ({ ctx }) => {
|
|
55
|
+
logger.info(`Confirming profile update for user ${ctx.session.user.id}`, 'AUTH');
|
|
56
|
+
await PusherService.emitProfileUpdate(ctx.session.user.id);
|
|
57
|
+
return { success: true };
|
|
58
|
+
}),
|
|
160
59
|
|
|
161
|
-
logger.info(`Profile picture asset created and linked for user ${userId}`, 'AUTH');
|
|
162
|
-
return {
|
|
163
|
-
success: true,
|
|
164
|
-
message: 'Profile picture uploaded successfully',
|
|
165
|
-
signedUrl: signedUrlData.signedUrl,
|
|
166
|
-
};
|
|
167
|
-
}),
|
|
168
|
-
confirmProfileUpdate: authedProcedure
|
|
169
|
-
.mutation(async ({ ctx }) => {
|
|
170
|
-
logger.info(`Confirming profile update for user ${ctx.session.user.id}`, 'AUTH');
|
|
171
|
-
await PusherService.emitProfileUpdate(ctx.session.user.id);
|
|
172
|
-
return { success: true };
|
|
173
|
-
}),
|
|
174
60
|
signup: publicProcedure
|
|
175
|
-
.input(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
184
|
-
if (existing) {
|
|
185
|
-
throw new TRPCError({ code: 'CONFLICT', message: "Email already registered" });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const hash = await bcrypt.hash(input.password, 10);
|
|
189
|
-
|
|
190
|
-
// Get default "User" role
|
|
191
|
-
const userRole = await ctx.db.role.findUnique({
|
|
192
|
-
where: { name: 'User' },
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const user = await ctx.db.user.create({
|
|
196
|
-
data: {
|
|
197
|
-
name: input.name,
|
|
198
|
-
email: input.email,
|
|
199
|
-
passwordHash: hash,
|
|
200
|
-
roleId: userRole?.id,
|
|
201
|
-
// emailVerified is null -- user must verify
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
await notifyAdminsOnSignup(ctx.db, {
|
|
206
|
-
id: user.id,
|
|
207
|
-
name: user.name,
|
|
208
|
-
email: user.email,
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// Create verification token (24h expiry)
|
|
212
|
-
const token = crypto.randomUUID();
|
|
213
|
-
await ctx.db.verificationToken.create({
|
|
214
|
-
data: {
|
|
215
|
-
identifier: input.email,
|
|
216
|
-
token,
|
|
217
|
-
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
218
|
-
},
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// Send verification email (non-blocking)
|
|
222
|
-
sendVerificationEmail(input.email, token, input.name).catch(() => { });
|
|
223
|
-
|
|
224
|
-
// Create Stripe Customer (non-blocking for registration, but we want it)
|
|
225
|
-
createStripeCustomer(input.email, input.name).then(async (stripeCustomerId) => {
|
|
226
|
-
if (stripeCustomerId) {
|
|
227
|
-
await ctx.db.user.update({
|
|
228
|
-
where: { id: user.id },
|
|
229
|
-
data: { stripe_customer_id: stripeCustomerId }
|
|
230
|
-
}).catch((err: any) => logger.error(`Failed to update user with stripe_customer_id: ${err.message}`, 'AUTH'));
|
|
231
|
-
}
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
return { id: user.id, email: user.email, name: user.name };
|
|
235
|
-
}),
|
|
61
|
+
.input(
|
|
62
|
+
z.object({
|
|
63
|
+
name: z.string().min(1),
|
|
64
|
+
email: z.string().email(),
|
|
65
|
+
password: passwordFieldSchema,
|
|
66
|
+
}),
|
|
67
|
+
)
|
|
68
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).signup(input)),
|
|
236
69
|
|
|
237
|
-
// Verify email with token from the email link
|
|
238
70
|
verifyEmail: publicProcedure
|
|
239
|
-
.input(z.object({
|
|
240
|
-
|
|
241
|
-
}))
|
|
242
|
-
.mutation(async ({ ctx, input }) => {
|
|
243
|
-
const record = await ctx.db.verificationToken.findUnique({
|
|
244
|
-
where: { token: input.token },
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
if (!record) {
|
|
248
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'Invalid or expired verification link' });
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
if (record.expires < new Date()) {
|
|
252
|
-
// Clean up expired token
|
|
253
|
-
await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
|
|
254
|
-
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Verification link has expired. Please request a new one.' });
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Mark user as verified
|
|
258
|
-
await ctx.db.user.update({
|
|
259
|
-
where: { email: record.identifier },
|
|
260
|
-
data: { emailVerified: new Date() },
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// Delete used token
|
|
264
|
-
await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
|
|
265
|
-
|
|
266
|
-
return { success: true, message: 'Email verified successfully' };
|
|
267
|
-
}),
|
|
268
|
-
|
|
269
|
-
// Resend verification email (for logged-in users who haven't verified)
|
|
270
|
-
resendVerification: publicProcedure
|
|
271
|
-
.mutation(async ({ ctx }) => {
|
|
272
|
-
if (!ctx.session?.user?.id) {
|
|
273
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not logged in' });
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const user = await ctx.db.user.findUnique({
|
|
277
|
-
where: { id: ctx.session.user.id },
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
if (!user || !user.email) {
|
|
281
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (user.emailVerified) {
|
|
285
|
-
return { success: true, message: 'Email is already verified' };
|
|
286
|
-
}
|
|
71
|
+
.input(z.object({ token: z.string() }))
|
|
72
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).verifyEmail(input.token)),
|
|
287
73
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
});
|
|
74
|
+
resendVerification: publicProcedure.mutation(({ ctx }) =>
|
|
75
|
+
new AuthService(ctx.db).resendVerification(ctx.session?.user?.id),
|
|
76
|
+
),
|
|
292
77
|
|
|
293
|
-
// Create new token
|
|
294
|
-
const token = crypto.randomUUID();
|
|
295
|
-
await ctx.db.verificationToken.create({
|
|
296
|
-
data: {
|
|
297
|
-
identifier: user.email,
|
|
298
|
-
token,
|
|
299
|
-
expires: new Date(Date.now() + 24 * 60 * 60 * 1000),
|
|
300
|
-
},
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
const sent = await sendVerificationEmail(user.email, token, user.name);
|
|
304
|
-
if (!sent) {
|
|
305
|
-
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Failed to send email. Please try again.' });
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return { success: true, message: 'Verification email sent' };
|
|
309
|
-
}),
|
|
310
78
|
login: publicProcedure
|
|
311
|
-
.input(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
79
|
+
.input(
|
|
80
|
+
z.object({
|
|
81
|
+
email: z.string().email(),
|
|
82
|
+
password: z.string().min(6),
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
315
85
|
.mutation(async ({ ctx, input }) => {
|
|
316
|
-
const
|
|
317
|
-
where: { email: input.email },
|
|
318
|
-
});
|
|
319
|
-
if (!user) {
|
|
320
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: "Invalid credentials" });
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
if (user.deletedAt) {
|
|
324
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: "Account scheduled for deletion. Please check your email for a restore link." });
|
|
325
|
-
}
|
|
86
|
+
const result = await new AuthService(ctx.db).login(input);
|
|
326
87
|
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
88
|
+
const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER) as boolean;
|
|
89
|
+
const cookieValue = serialize(
|
|
90
|
+
'auth_token',
|
|
91
|
+
result.token,
|
|
92
|
+
getAuthCookieConfig(isProduction),
|
|
93
|
+
);
|
|
94
|
+
ctx.res.setHeader('Set-Cookie', cookieValue);
|
|
331
95
|
|
|
332
|
-
|
|
333
|
-
const authToken = createCustomAuthToken(user.id);
|
|
334
|
-
|
|
335
|
-
const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER) as boolean;
|
|
336
|
-
|
|
337
|
-
const cookieValue = serialize("auth_token", authToken, getAuthCookieConfig(isProduction));
|
|
338
|
-
|
|
339
|
-
ctx.res.setHeader("Set-Cookie", cookieValue);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
return {
|
|
343
|
-
id: user.id,
|
|
344
|
-
email: user.email,
|
|
345
|
-
name: user.name,
|
|
346
|
-
token: authToken
|
|
347
|
-
};
|
|
96
|
+
return result;
|
|
348
97
|
}),
|
|
349
98
|
|
|
350
|
-
/**
|
|
351
|
-
* Request a password reset email. Always returns the same message (no email enumeration).
|
|
352
|
-
*/
|
|
353
99
|
requestPasswordReset: publicProcedure
|
|
354
100
|
.input(z.object({ email: z.string().email() }))
|
|
355
|
-
.mutation(
|
|
356
|
-
const email = input.email.trim().toLowerCase();
|
|
357
|
-
const generic = {
|
|
358
|
-
success: true as const,
|
|
359
|
-
message:
|
|
360
|
-
'If an account exists for this email, we sent password reset instructions.',
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
const user = await ctx.db.user.findUnique({
|
|
364
|
-
where: { email },
|
|
365
|
-
select: {
|
|
366
|
-
id: true,
|
|
367
|
-
email: true,
|
|
368
|
-
name: true,
|
|
369
|
-
passwordHash: true,
|
|
370
|
-
deletedAt: true,
|
|
371
|
-
},
|
|
372
|
-
});
|
|
101
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).requestPasswordReset(input)),
|
|
373
102
|
|
|
374
|
-
if (!user?.passwordHash || user.deletedAt || !user.email) {
|
|
375
|
-
return generic;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
await passwordResetDb(ctx).deleteMany({
|
|
379
|
-
where: { userId: user.id, usedAt: null },
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
const rawToken = crypto.randomBytes(32).toString('hex');
|
|
383
|
-
const tokenHash = hashPasswordResetToken(rawToken);
|
|
384
|
-
const expiresAt = new Date(Date.now() + 60 * 60 * 1000);
|
|
385
|
-
|
|
386
|
-
await passwordResetDb(ctx).create({
|
|
387
|
-
data: {
|
|
388
|
-
userId: user.id,
|
|
389
|
-
tokenHash,
|
|
390
|
-
expiresAt,
|
|
391
|
-
},
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
sendPasswordResetEmail(user.email, rawToken, user.name).catch(() => {});
|
|
395
|
-
|
|
396
|
-
return generic;
|
|
397
|
-
}),
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Complete password reset using the token from the email link.
|
|
401
|
-
*/
|
|
402
103
|
resetPassword: publicProcedure
|
|
403
104
|
.input(
|
|
404
105
|
z.object({
|
|
405
106
|
token: z.string().min(1),
|
|
406
107
|
newPassword: passwordFieldSchema,
|
|
407
|
-
})
|
|
108
|
+
}),
|
|
408
109
|
)
|
|
409
|
-
.mutation(
|
|
410
|
-
const tokenHash = hashPasswordResetToken(input.token);
|
|
411
|
-
|
|
412
|
-
const record = await passwordResetDb(ctx).findUnique({
|
|
413
|
-
where: { tokenHash },
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
if (!record || record.usedAt || record.expiresAt < new Date()) {
|
|
417
|
-
throw new TRPCError({
|
|
418
|
-
code: 'BAD_REQUEST',
|
|
419
|
-
message: 'Invalid or expired reset link. Please request a new one.',
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const newHash = await bcrypt.hash(input.newPassword, 10);
|
|
424
|
-
|
|
425
|
-
await ctx.db.user.update({
|
|
426
|
-
where: { id: record.userId },
|
|
427
|
-
data: { passwordHash: newHash },
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
await passwordResetDb(ctx).update({
|
|
431
|
-
where: { id: record.id },
|
|
432
|
-
data: { usedAt: new Date() },
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
await passwordResetDb(ctx).deleteMany({
|
|
436
|
-
where: { userId: record.userId, id: { not: record.id } },
|
|
437
|
-
});
|
|
110
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).resetPassword(input)),
|
|
438
111
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
getSession: publicProcedure.query(async ({ ctx }) => {
|
|
443
|
-
// Just return the current session from context
|
|
444
|
-
if (!ctx.session) {
|
|
445
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message: "No session found" });
|
|
446
|
-
}
|
|
112
|
+
getSession: publicProcedure.query(({ ctx }) =>
|
|
113
|
+
new AuthService(ctx.db).getSession((ctx.session as any)?.user?.id),
|
|
114
|
+
),
|
|
447
115
|
|
|
448
|
-
|
|
449
|
-
const
|
|
450
|
-
where: { id: userId },
|
|
451
|
-
include: {
|
|
452
|
-
profilePicture: true,
|
|
453
|
-
role: true,
|
|
454
|
-
}
|
|
455
|
-
});
|
|
116
|
+
requestAccountDeletion: authedProcedure.mutation(async ({ ctx }) => {
|
|
117
|
+
const result = await new AuthService(ctx.db).requestAccountDeletion(ctx.session.user.id);
|
|
456
118
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
: null;
|
|
464
|
-
|
|
465
|
-
logger.info(`Session fetched for user ${userId}, profilePicture: ${profilePictureUrl}`, 'AUTH');
|
|
119
|
+
const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER) as boolean;
|
|
120
|
+
const clearCookieConfig = getAuthCookieConfig(isProduction);
|
|
121
|
+
ctx.res.setHeader(
|
|
122
|
+
'Set-Cookie',
|
|
123
|
+
serialize('auth_token', '', { ...clearCookieConfig, maxAge: 0 }),
|
|
124
|
+
);
|
|
466
125
|
|
|
467
|
-
return
|
|
468
|
-
user: {
|
|
469
|
-
id: user.id,
|
|
470
|
-
email: user.email,
|
|
471
|
-
name: user.name,
|
|
472
|
-
emailVerified: !!user.emailVerified,
|
|
473
|
-
profilePicture: profilePictureUrl,
|
|
474
|
-
role: user.role,
|
|
475
|
-
}
|
|
476
|
-
};
|
|
126
|
+
return result;
|
|
477
127
|
}),
|
|
478
|
-
requestAccountDeletion: authedProcedure
|
|
479
|
-
.mutation(async ({ ctx }) => {
|
|
480
|
-
const user = await ctx.db.user.findUnique({
|
|
481
|
-
where: { id: ctx.session.user.id },
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
if (!user) {
|
|
485
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'User not found' });
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Mark user as deleted
|
|
489
|
-
await ctx.db.user.update({
|
|
490
|
-
where: { id: user.id },
|
|
491
|
-
data: { deletedAt: new Date() },
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
await notifyAdminsAccountDeletionScheduled(ctx.db, {
|
|
495
|
-
id: user.id,
|
|
496
|
-
name: user.name,
|
|
497
|
-
email: user.email,
|
|
498
|
-
}).catch(() => {});
|
|
499
|
-
|
|
500
|
-
// Clear existing restore tokens
|
|
501
|
-
await ctx.db.verificationToken.deleteMany({
|
|
502
|
-
where: { identifier: `restore-${user.email}` },
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
// Create restore token (30 days expiry)
|
|
506
|
-
const token = crypto.randomUUID();
|
|
507
|
-
await ctx.db.verificationToken.create({
|
|
508
|
-
data: {
|
|
509
|
-
identifier: `restore-${user.email}`,
|
|
510
|
-
token,
|
|
511
|
-
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
512
|
-
},
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
// Send email
|
|
516
|
-
if (user.email) {
|
|
517
|
-
sendAccountDeletionScheduledEmail(user.email, token).catch(() => { });
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Log out user by clearing cookie
|
|
521
|
-
const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER) as boolean;
|
|
522
|
-
const clearCookieConfig = getAuthCookieConfig(isProduction);
|
|
523
|
-
ctx.res.setHeader("Set-Cookie", serialize("auth_token", "", {
|
|
524
|
-
...clearCookieConfig,
|
|
525
|
-
maxAge: 0,
|
|
526
|
-
}));
|
|
527
|
-
|
|
528
|
-
return { success: true, message: 'Account scheduled for deletion' };
|
|
529
|
-
}),
|
|
530
128
|
|
|
531
129
|
restoreAccount: publicProcedure
|
|
532
|
-
.input(z.object({
|
|
533
|
-
|
|
534
|
-
}))
|
|
535
|
-
.mutation(async ({ ctx, input }) => {
|
|
536
|
-
const record = await ctx.db.verificationToken.findUnique({
|
|
537
|
-
where: { token: input.token },
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
if (!record || !record.identifier.startsWith('restore-')) {
|
|
541
|
-
throw new TRPCError({ code: 'NOT_FOUND', message: 'Invalid or expired restore link' });
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
if (record.expires < new Date()) {
|
|
545
|
-
await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
|
|
546
|
-
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Restore link has expired.' });
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const email = record.identifier.replace('restore-', '');
|
|
550
|
-
|
|
551
|
-
// Mark user as restored
|
|
552
|
-
await ctx.db.user.update({
|
|
553
|
-
where: { email },
|
|
554
|
-
data: { deletedAt: null },
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
// Delete used token
|
|
558
|
-
await ctx.db.verificationToken.deleteMany({ where: { token: input.token } });
|
|
559
|
-
|
|
560
|
-
// Send confirmation email
|
|
561
|
-
sendAccountRestoredEmail(email).catch(() => { });
|
|
562
|
-
|
|
563
|
-
return { success: true, message: 'Account restored successfully' };
|
|
564
|
-
}),
|
|
130
|
+
.input(z.object({ token: z.string() }))
|
|
131
|
+
.mutation(({ ctx, input }) => new AuthService(ctx.db).restoreAccount(input.token)),
|
|
565
132
|
|
|
566
133
|
logout: publicProcedure.mutation(async ({ ctx }) => {
|
|
567
|
-
const token = ctx.cookies[
|
|
134
|
+
const token = ctx.cookies['auth_token'];
|
|
568
135
|
|
|
569
136
|
if (!token) {
|
|
570
|
-
throw new TRPCError({ code: 'UNAUTHORIZED', message:
|
|
137
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'No token found' });
|
|
571
138
|
}
|
|
572
139
|
|
|
573
|
-
|
|
574
|
-
// custom HMAC auth system (auth_token cookie).
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const isProduction = (process.env.NODE_ENV === "production" || process.env.RENDER) as boolean;
|
|
140
|
+
const isProduction = (process.env.NODE_ENV === 'production' || process.env.RENDER) as boolean;
|
|
578
141
|
const clearCookieConfig = getAuthCookieConfig(isProduction);
|
|
579
|
-
ctx.res.setHeader(
|
|
580
|
-
|
|
581
|
-
maxAge: 0,
|
|
582
|
-
|
|
142
|
+
ctx.res.setHeader(
|
|
143
|
+
'Set-Cookie',
|
|
144
|
+
serialize('auth_token', '', { ...clearCookieConfig, maxAge: 0 }),
|
|
145
|
+
);
|
|
583
146
|
|
|
584
147
|
return { success: true };
|
|
585
148
|
}),
|
|
586
149
|
});
|
|
587
|
-
|