create-stackr 0.2.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/LICENSE +21 -0
- package/README.md +642 -0
- package/bin/cli.js +12 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +113 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/dependencies.d.ts +82 -0
- package/dist/config/dependencies.d.ts.map +1 -0
- package/dist/config/dependencies.js +82 -0
- package/dist/config/dependencies.js.map +1 -0
- package/dist/config/presets.d.ts +3 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/presets.js +174 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/generators/index.d.ts +40 -0
- package/dist/generators/index.d.ts.map +1 -0
- package/dist/generators/index.js +130 -0
- package/dist/generators/index.js.map +1 -0
- package/dist/generators/onboarding.d.ts +8 -0
- package/dist/generators/onboarding.d.ts.map +1 -0
- package/dist/generators/onboarding.js +141 -0
- package/dist/generators/onboarding.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/features.d.ts +14 -0
- package/dist/prompts/features.d.ts.map +1 -0
- package/dist/prompts/features.js +96 -0
- package/dist/prompts/features.js.map +1 -0
- package/dist/prompts/index.d.ts +3 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +93 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/onboarding.d.ts +6 -0
- package/dist/prompts/onboarding.d.ts.map +1 -0
- package/dist/prompts/onboarding.js +37 -0
- package/dist/prompts/onboarding.js.map +1 -0
- package/dist/prompts/orm.d.ts +3 -0
- package/dist/prompts/orm.d.ts.map +1 -0
- package/dist/prompts/orm.js +23 -0
- package/dist/prompts/orm.js.map +1 -0
- package/dist/prompts/packageManager.d.ts +2 -0
- package/dist/prompts/packageManager.d.ts.map +1 -0
- package/dist/prompts/packageManager.js +18 -0
- package/dist/prompts/packageManager.js.map +1 -0
- package/dist/prompts/platform.d.ts +3 -0
- package/dist/prompts/platform.d.ts.map +1 -0
- package/dist/prompts/platform.js +21 -0
- package/dist/prompts/platform.js.map +1 -0
- package/dist/prompts/preset.d.ts +4 -0
- package/dist/prompts/preset.d.ts.map +1 -0
- package/dist/prompts/preset.js +165 -0
- package/dist/prompts/preset.js.map +1 -0
- package/dist/prompts/project.d.ts +2 -0
- package/dist/prompts/project.d.ts.map +1 -0
- package/dist/prompts/project.js +27 -0
- package/dist/prompts/project.js.map +1 -0
- package/dist/prompts/sdks.d.ts +2 -0
- package/dist/prompts/sdks.d.ts.map +1 -0
- package/dist/prompts/sdks.js +46 -0
- package/dist/prompts/sdks.js.map +1 -0
- package/dist/types/index.d.ts +77 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +25 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/cleanup.d.ts +5 -0
- package/dist/utils/cleanup.d.ts.map +1 -0
- package/dist/utils/cleanup.js +38 -0
- package/dist/utils/cleanup.js.map +1 -0
- package/dist/utils/copy.d.ts +10 -0
- package/dist/utils/copy.d.ts.map +1 -0
- package/dist/utils/copy.js +53 -0
- package/dist/utils/copy.js.map +1 -0
- package/dist/utils/errors.d.ts +33 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +136 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/git.d.ts +5 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +33 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +22 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/package.d.ts +16 -0
- package/dist/utils/package.d.ts.map +1 -0
- package/dist/utils/package.js +86 -0
- package/dist/utils/package.js.map +1 -0
- package/dist/utils/system-validation.d.ts +9 -0
- package/dist/utils/system-validation.d.ts.map +1 -0
- package/dist/utils/system-validation.js +31 -0
- package/dist/utils/system-validation.js.map +1 -0
- package/dist/utils/template.d.ts +20 -0
- package/dist/utils/template.d.ts.map +1 -0
- package/dist/utils/template.js +234 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +94 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +96 -0
- package/templates/base/backend/.dockerignore.ejs +62 -0
- package/templates/base/backend/.env.example.ejs +116 -0
- package/templates/base/backend/Dockerfile.ejs +142 -0
- package/templates/base/backend/controllers/event-queue/index.ts +20 -0
- package/templates/base/backend/controllers/event-queue/workers/user.ts +39 -0
- package/templates/base/backend/controllers/rest-api/index.ts +48 -0
- package/templates/base/backend/controllers/rest-api/plugins/auth.ts +152 -0
- package/templates/base/backend/controllers/rest-api/plugins/config.ts +64 -0
- package/templates/base/backend/controllers/rest-api/plugins/error-handler.ts +118 -0
- package/templates/base/backend/controllers/rest-api/routes/auth.ts.ejs +180 -0
- package/templates/base/backend/controllers/rest-api/routes/device-sessions.ts +197 -0
- package/templates/base/backend/controllers/rest-api/routes/oauth-web.ts.ejs +375 -0
- package/templates/base/backend/controllers/rest-api/server.ts.ejs +87 -0
- package/templates/base/backend/domain/device-session/repository.drizzle.ts +209 -0
- package/templates/base/backend/domain/device-session/repository.prisma.ts +248 -0
- package/templates/base/backend/domain/device-session/schema.ts +72 -0
- package/templates/base/backend/domain/session/repository.drizzle.ts +72 -0
- package/templates/base/backend/domain/session/repository.prisma.ts +72 -0
- package/templates/base/backend/domain/session/schema.ts +29 -0
- package/templates/base/backend/domain/user/repository.drizzle.ts +127 -0
- package/templates/base/backend/domain/user/repository.prisma.ts +115 -0
- package/templates/base/backend/domain/user/schema.ts +14 -0
- package/templates/base/backend/drizzle/schema.drizzle.ts +111 -0
- package/templates/base/backend/drizzle.config.drizzle.ts +13 -0
- package/templates/base/backend/lib/auth.drizzle.ts.ejs +104 -0
- package/templates/base/backend/lib/auth.prisma.ts.ejs +97 -0
- package/templates/base/backend/lib/constants.ts.ejs +29 -0
- package/templates/base/backend/package.json.ejs +50 -0
- package/templates/base/backend/prisma/schema.prisma.ejs +102 -0
- package/templates/base/backend/prisma.config.prisma.ts +12 -0
- package/templates/base/backend/tsconfig.json +39 -0
- package/templates/base/backend/utils/db.drizzle.ts +41 -0
- package/templates/base/backend/utils/db.prisma.ts +51 -0
- package/templates/base/backend/utils/email.ts.ejs +35 -0
- package/templates/base/backend/utils/errors.ts +348 -0
- package/templates/base/backend/utils/redis.ts.ejs +279 -0
- package/templates/base/mobile/.env.example.ejs +35 -0
- package/templates/base/mobile/.gitignore.ejs +167 -0
- package/templates/base/mobile/app/+not-found.tsx +85 -0
- package/templates/base/mobile/app/_layout.tsx.ejs +71 -0
- package/templates/base/mobile/app.json.ejs +88 -0
- package/templates/base/mobile/assets/images/adaptive-icon.png +0 -0
- package/templates/base/mobile/assets/images/favicon.png +0 -0
- package/templates/base/mobile/assets/images/icon.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_1.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_2.png +0 -0
- package/templates/base/mobile/assets/images/onboarding_page_3.png +0 -0
- package/templates/base/mobile/assets/images/paywall_image.png +0 -0
- package/templates/base/mobile/assets/images/splash.png +0 -0
- package/templates/base/mobile/eas.json.ejs +49 -0
- package/templates/base/mobile/metro.config.js +9 -0
- package/templates/base/mobile/package.json.ejs +53 -0
- package/templates/base/mobile/src/components/ui/Button.tsx +131 -0
- package/templates/base/mobile/src/components/ui/Card.tsx +68 -0
- package/templates/base/mobile/src/components/ui/IconSymbol.tsx +90 -0
- package/templates/base/mobile/src/components/ui/Input.tsx +142 -0
- package/templates/base/mobile/src/components/ui/LoadingSpinner.tsx +98 -0
- package/templates/base/mobile/src/components/ui/OnboardingLayout.tsx +356 -0
- package/templates/base/mobile/src/components/ui/PaywallLayout.tsx +311 -0
- package/templates/base/mobile/src/components/ui/Skeleton.tsx +58 -0
- package/templates/base/mobile/src/components/ui/index.ts +6 -0
- package/templates/base/mobile/src/constants/Theme.ts +163 -0
- package/templates/base/mobile/src/context/ThemeContext.tsx +157 -0
- package/templates/base/mobile/src/lib/auth-client.ts.ejs +51 -0
- package/templates/base/mobile/src/services/api.ts.ejs +71 -0
- package/templates/base/mobile/src/services/errorService.ts +179 -0
- package/templates/base/mobile/src/services/sdkInitializer.ts.ejs +36 -0
- package/templates/base/mobile/src/store/index.ts.ejs +18 -0
- package/templates/base/mobile/src/store/ui.store.ts +100 -0
- package/templates/base/mobile/src/utils/formatters.ts +105 -0
- package/templates/base/mobile/src/utils/logger.ts +73 -0
- package/templates/base/mobile/src/utils/responsive.ts +234 -0
- package/templates/base/mobile/tsconfig.json +32 -0
- package/templates/base/web/.env.example.ejs +26 -0
- package/templates/base/web/components.json +22 -0
- package/templates/base/web/eslint.config.mjs +18 -0
- package/templates/base/web/next.config.ts +7 -0
- package/templates/base/web/package.json.ejs +35 -0
- package/templates/base/web/postcss.config.mjs +7 -0
- package/templates/base/web/public/.gitkeep +0 -0
- package/templates/base/web/public/file.svg +1 -0
- package/templates/base/web/public/globe.svg +1 -0
- package/templates/base/web/public/next.svg +1 -0
- package/templates/base/web/public/vercel.svg +1 -0
- package/templates/base/web/public/window.svg +1 -0
- package/templates/base/web/src/app/favicon.ico +0 -0
- package/templates/base/web/src/app/globals.css +152 -0
- package/templates/base/web/src/app/layout.tsx.ejs +54 -0
- package/templates/base/web/src/app/page.tsx.ejs +92 -0
- package/templates/base/web/src/components/auth/auth-hydrator.tsx.ejs +19 -0
- package/templates/base/web/src/components/auth/protected-route.tsx.ejs +109 -0
- package/templates/base/web/src/components/providers/device-session-setup.tsx.ejs +56 -0
- package/templates/base/web/src/components/providers/theme-provider.tsx +17 -0
- package/templates/base/web/src/components/theme-toggle.tsx +34 -0
- package/templates/base/web/src/components/ui/button.tsx +62 -0
- package/templates/base/web/src/components/ui/card.tsx +92 -0
- package/templates/base/web/src/components/ui/input.tsx +21 -0
- package/templates/base/web/src/components/ui/label.tsx +24 -0
- package/templates/base/web/src/components/ui/skeleton.tsx +13 -0
- package/templates/base/web/src/components/ui/spinner.tsx +20 -0
- package/templates/base/web/src/hooks/use-device-session.ts.ejs +40 -0
- package/templates/base/web/src/hooks/use-session.ts.ejs +56 -0
- package/templates/base/web/src/lib/auth/actions.ts.ejs +334 -0
- package/templates/base/web/src/lib/auth/config.ts.ejs +65 -0
- package/templates/base/web/src/lib/auth/cookies.ts.ejs +74 -0
- package/templates/base/web/src/lib/auth/index.ts.ejs +40 -0
- package/templates/base/web/src/lib/auth/oauth.ts.ejs +72 -0
- package/templates/base/web/src/lib/auth/pkce.ts.ejs +48 -0
- package/templates/base/web/src/lib/auth/sessions.ts.ejs +135 -0
- package/templates/base/web/src/lib/auth/user-agent.ts.ejs +47 -0
- package/templates/base/web/src/lib/device/actions.ts.ejs +148 -0
- package/templates/base/web/src/lib/device/id.ts.ejs +74 -0
- package/templates/base/web/src/lib/utils.ts +6 -0
- package/templates/base/web/src/proxy.ts.ejs +66 -0
- package/templates/base/web/src/store/auth.store.ts.ejs +89 -0
- package/templates/base/web/src/store/deviceSession.store.ts.ejs +141 -0
- package/templates/base/web/tsconfig.json +34 -0
- package/templates/features/mobile/auth/app/(auth)/_layout.tsx +16 -0
- package/templates/features/mobile/auth/app/(auth)/login.tsx +86 -0
- package/templates/features/mobile/auth/app/(auth)/register.tsx +86 -0
- package/templates/features/mobile/auth/components/auth/LoginForm.tsx.ejs +349 -0
- package/templates/features/mobile/auth/components/auth/RegisterForm.tsx.ejs +407 -0
- package/templates/features/mobile/auth/components/auth/index.ts +2 -0
- package/templates/features/mobile/auth/hooks/index.ts.ejs +1 -0
- package/templates/features/mobile/auth/hooks/useAuth.ts.ejs +367 -0
- package/templates/features/mobile/auth/services/deviceSession.ts +370 -0
- package/templates/features/mobile/auth/store/deviceSession.store.ts +326 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/_layout.tsx.ejs +11 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-1.tsx.ejs +52 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-2.tsx.ejs +52 -0
- package/templates/features/mobile/onboarding/app/(onboarding)/page-3.tsx.ejs +60 -0
- package/templates/features/mobile/paywall/app/paywall.tsx +550 -0
- package/templates/features/mobile/tabs/app/(tabs)/_layout.tsx +26 -0
- package/templates/features/mobile/tabs/app/(tabs)/index.tsx +565 -0
- package/templates/features/web/.gitkeep +0 -0
- package/templates/features/web/auth/app/(app)/dashboard/dashboard-client.tsx.ejs +166 -0
- package/templates/features/web/auth/app/(app)/dashboard/page.tsx.ejs +24 -0
- package/templates/features/web/auth/app/(app)/layout.tsx.ejs +43 -0
- package/templates/features/web/auth/app/(app)/settings/sessions/page.tsx.ejs +29 -0
- package/templates/features/web/auth/app/(app)/settings/sessions/sessions-client.tsx.ejs +77 -0
- package/templates/features/web/auth/app/(auth)/forgot-password/page.tsx.ejs +127 -0
- package/templates/features/web/auth/app/(auth)/layout.tsx.ejs +32 -0
- package/templates/features/web/auth/app/(auth)/login/page.tsx.ejs +35 -0
- package/templates/features/web/auth/app/(auth)/register/page.tsx.ejs +19 -0
- package/templates/features/web/auth/app/(auth)/reset-password/page.tsx.ejs +40 -0
- package/templates/features/web/auth/app/(auth)/verify-email/page.tsx.ejs +198 -0
- package/templates/features/web/auth/app/auth/callback/route.ts.ejs +152 -0
- package/templates/features/web/auth/components/auth/login-form.tsx.ejs +100 -0
- package/templates/features/web/auth/components/auth/oauth-buttons.tsx.ejs +126 -0
- package/templates/features/web/auth/components/auth/password-reset-form.tsx.ejs +103 -0
- package/templates/features/web/auth/components/auth/register-form.tsx.ejs +139 -0
- package/templates/features/web/auth/components/settings/session-card.tsx.ejs +132 -0
- package/templates/integrations/mobile/adjust/services/adjustService.ts.ejs +163 -0
- package/templates/integrations/mobile/adjust/store/adjust.store.ts +243 -0
- package/templates/integrations/mobile/att/services/attService.ts +84 -0
- package/templates/integrations/mobile/att/services/trackingPermissions.ts +208 -0
- package/templates/integrations/mobile/att/store/att.store.ts +162 -0
- package/templates/integrations/mobile/revenuecat/services/revenuecatService.ts.ejs +174 -0
- package/templates/integrations/mobile/revenuecat/store/revenuecat.store.ts +286 -0
- package/templates/integrations/mobile/scate/services/scateService.ts.ejs +85 -0
- package/templates/integrations/mobile/scate/store/scate.store.ts +125 -0
- package/templates/integrations/web/.gitkeep +0 -0
- package/templates/shared/.env.example.ejs +21 -0
- package/templates/shared/.gitignore.ejs +145 -0
- package/templates/shared/README.md.ejs +134 -0
- package/templates/shared/docker-compose.prod.yml.ejs +120 -0
- package/templates/shared/docker-compose.yml.ejs +129 -0
- package/templates/shared/scripts/docker-dev.sh.ejs +395 -0
- package/templates/shared/scripts/docker-prod.sh.ejs +542 -0
- package/templates/shared/scripts/setup.sh.ejs +979 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { AuthFastifyRequest } from "fastify";
|
|
2
|
+
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
3
|
+
import { auth } from "../../../lib/auth";
|
|
4
|
+
import { Type } from "@sinclair/typebox";
|
|
5
|
+
import { updateUserProfile, deleteUser } from "../../../domain/user/repository";
|
|
6
|
+
import { findUserSessionById, revokeSessionByToken } from "../../../domain/session/repository";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Convert Fastify request to Fetch API Request for BetterAuth
|
|
10
|
+
* BetterAuth expects a standard Fetch API Request object
|
|
11
|
+
*/
|
|
12
|
+
function toFetchRequest(request: FastifyRequest): Request {
|
|
13
|
+
// Construct full URL
|
|
14
|
+
const url = `${request.protocol}://${request.hostname}${request.url}`;
|
|
15
|
+
|
|
16
|
+
// Convert Fastify headers to Fetch API Headers
|
|
17
|
+
const headers = new Headers();
|
|
18
|
+
Object.entries(request.headers).forEach(([key, value]) => {
|
|
19
|
+
if (value) {
|
|
20
|
+
headers.set(key, Array.isArray(value) ? value[0] : value);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Create Fetch API compatible Request
|
|
25
|
+
// Note: GET and HEAD requests cannot have a body
|
|
26
|
+
const hasBody = request.method !== "GET" && request.method !== "HEAD" && request.body;
|
|
27
|
+
|
|
28
|
+
return new Request(url, {
|
|
29
|
+
method: request.method,
|
|
30
|
+
headers,
|
|
31
|
+
body: hasBody ? JSON.stringify(request.body) : undefined,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const authRoutes: FastifyPluginAsync = async (server) => {
|
|
36
|
+
// Mount BetterAuth handler for all /* routes under this plugin
|
|
37
|
+
// Since this plugin is registered at /api/auth, BetterAuth handles /api/auth/*
|
|
38
|
+
// BetterAuth handles: sign-in, sign-up, sign-out, oauth callbacks, etc.
|
|
39
|
+
server.all("/*", async (request, reply) => {
|
|
40
|
+
// Convert Fastify request to Fetch API Request for BetterAuth
|
|
41
|
+
const fetchRequest = toFetchRequest(request);
|
|
42
|
+
const response = await auth.handler(fetchRequest);
|
|
43
|
+
|
|
44
|
+
// Copy response headers
|
|
45
|
+
response.headers.forEach((value, key) => {
|
|
46
|
+
reply.header(key, value);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Send response body
|
|
50
|
+
// Use text() to get the response body as BetterAuth may return JSON or other content
|
|
51
|
+
const body = await response.text();
|
|
52
|
+
reply.status(response.status).send(body);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Keep custom endpoints that extend BetterAuth functionality
|
|
56
|
+
|
|
57
|
+
// Get current user (uses BetterAuth session)
|
|
58
|
+
server.get(
|
|
59
|
+
"/me",
|
|
60
|
+
{
|
|
61
|
+
onRequest: server.requireAuth,
|
|
62
|
+
schema: {
|
|
63
|
+
response: {
|
|
64
|
+
200: Type.Object({
|
|
65
|
+
id: Type.String(),
|
|
66
|
+
email: Type.String(),
|
|
67
|
+
name: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
68
|
+
emailVerified: Type.Boolean(),
|
|
69
|
+
image: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
70
|
+
createdAt: Type.String(),
|
|
71
|
+
updatedAt: Type.String(),
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
async (request, reply) => {
|
|
77
|
+
// user is attached by requireAuth middleware
|
|
78
|
+
const { user } = request as AuthFastifyRequest;
|
|
79
|
+
return reply.send({
|
|
80
|
+
id: user.id,
|
|
81
|
+
email: user.email,
|
|
82
|
+
name: user.name,
|
|
83
|
+
emailVerified: user.emailVerified,
|
|
84
|
+
image: user.image,
|
|
85
|
+
createdAt: user.createdAt.toISOString(),
|
|
86
|
+
updatedAt: user.updatedAt.toISOString(),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Update profile (custom endpoint, BetterAuth doesn't provide this)
|
|
92
|
+
server.put(
|
|
93
|
+
"/profile",
|
|
94
|
+
{
|
|
95
|
+
onRequest: server.requireAuth,
|
|
96
|
+
schema: {
|
|
97
|
+
body: Type.Object({
|
|
98
|
+
name: Type.Optional(Type.String()),
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
async (request, reply) => {
|
|
103
|
+
const { user } = request as AuthFastifyRequest;
|
|
104
|
+
const { name } = request.body as { name?: string };
|
|
105
|
+
|
|
106
|
+
const updatedUser = await updateUserProfile(user.id, { name });
|
|
107
|
+
|
|
108
|
+
return reply.send({
|
|
109
|
+
id: updatedUser.id,
|
|
110
|
+
email: updatedUser.email,
|
|
111
|
+
name: updatedUser.name,
|
|
112
|
+
emailVerified: updatedUser.emailVerified,
|
|
113
|
+
image: updatedUser.image,
|
|
114
|
+
createdAt: updatedUser.createdAt.toISOString(),
|
|
115
|
+
updatedAt: updatedUser.updatedAt.toISOString(),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Delete account
|
|
121
|
+
server.delete(
|
|
122
|
+
"/account",
|
|
123
|
+
{
|
|
124
|
+
onRequest: server.requireAuth,
|
|
125
|
+
},
|
|
126
|
+
async (request, reply) => {
|
|
127
|
+
const { user } = request as AuthFastifyRequest;
|
|
128
|
+
|
|
129
|
+
await deleteUser(user.id);
|
|
130
|
+
|
|
131
|
+
return reply.send({ message: "Account deleted successfully" });
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// Revoke a specific session by ID (BFF endpoint)
|
|
136
|
+
// Better Auth's native revokeSession requires the token, but list-sessions
|
|
137
|
+
// doesn't expose tokens for security. This endpoint looks up the token by ID
|
|
138
|
+
// and calls the native Better Auth revocation.
|
|
139
|
+
server.post(
|
|
140
|
+
"/revoke-session-by-id",
|
|
141
|
+
{
|
|
142
|
+
onRequest: server.requireAuth,
|
|
143
|
+
schema: {
|
|
144
|
+
body: Type.Object({
|
|
145
|
+
sessionId: Type.String(),
|
|
146
|
+
}),
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
async (request, reply) => {
|
|
150
|
+
const { user } = request as AuthFastifyRequest;
|
|
151
|
+
const { sessionId } = request.body as { sessionId: string };
|
|
152
|
+
|
|
153
|
+
// Find the session (validates it belongs to user)
|
|
154
|
+
const session = await findUserSessionById(sessionId, user.id);
|
|
155
|
+
|
|
156
|
+
if (!session) {
|
|
157
|
+
return reply.status(404).send({ error: "Session not found" });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Revoke using Better Auth's native method
|
|
161
|
+
const response = await revokeSessionByToken(
|
|
162
|
+
session.token,
|
|
163
|
+
request.headers as unknown as Headers
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
return reply.send(response);
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Health check
|
|
171
|
+
server.get("/health", async (_request, reply) => {
|
|
172
|
+
return reply.send({
|
|
173
|
+
status: "ok",
|
|
174
|
+
timestamp: new Date().toISOString(),
|
|
175
|
+
service: "auth",
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export default authRoutes;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import type { DeviceSessionFastifyRequest } from "fastify";
|
|
2
|
+
import { FastifyPluginAsync } from "fastify";
|
|
3
|
+
import {
|
|
4
|
+
CreateDeviceSessionBodySchema,
|
|
5
|
+
CreateDeviceSessionResponseSchema,
|
|
6
|
+
ValidateDeviceSessionBodySchema,
|
|
7
|
+
DeviceSessionValidationResponseSchema,
|
|
8
|
+
UpdateDeviceSessionActivityBodySchema,
|
|
9
|
+
DeviceSessionMigrationEligibilityResponseSchema,
|
|
10
|
+
} from "../../../domain/device-session/schema";
|
|
11
|
+
import {
|
|
12
|
+
createDeviceSession,
|
|
13
|
+
validateDeviceSession,
|
|
14
|
+
updateDeviceSessionActivity,
|
|
15
|
+
validateDeviceSessionMigrationEligibility,
|
|
16
|
+
deleteDeviceSession,
|
|
17
|
+
cleanupExpiredDeviceSessions,
|
|
18
|
+
} from "../../../domain/device-session/repository";
|
|
19
|
+
import { Type } from "@sinclair/typebox";
|
|
20
|
+
|
|
21
|
+
const deviceSessionRoutes: FastifyPluginAsync = async (server) => {
|
|
22
|
+
// Create new anonymous device session
|
|
23
|
+
server.post<{
|
|
24
|
+
Body: typeof CreateDeviceSessionBodySchema._type;
|
|
25
|
+
Reply: typeof CreateDeviceSessionResponseSchema._type;
|
|
26
|
+
}>(
|
|
27
|
+
"/",
|
|
28
|
+
{
|
|
29
|
+
schema: {
|
|
30
|
+
body: CreateDeviceSessionBodySchema,
|
|
31
|
+
response: {
|
|
32
|
+
201: CreateDeviceSessionResponseSchema,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
async (request, reply) => {
|
|
37
|
+
const { deviceId } = request.body as typeof CreateDeviceSessionBodySchema._type;
|
|
38
|
+
|
|
39
|
+
const result = await createDeviceSession({ deviceId });
|
|
40
|
+
return reply.status(201).send(result);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Validate device session token
|
|
45
|
+
server.post<{
|
|
46
|
+
Body: typeof ValidateDeviceSessionBodySchema._type;
|
|
47
|
+
Reply: typeof DeviceSessionValidationResponseSchema._type;
|
|
48
|
+
}>(
|
|
49
|
+
"/validate",
|
|
50
|
+
{
|
|
51
|
+
schema: {
|
|
52
|
+
body: ValidateDeviceSessionBodySchema,
|
|
53
|
+
response: {
|
|
54
|
+
200: DeviceSessionValidationResponseSchema,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
async (request, reply) => {
|
|
59
|
+
const { sessionToken } = request.body as typeof ValidateDeviceSessionBodySchema._type;
|
|
60
|
+
|
|
61
|
+
const session = await validateDeviceSession(sessionToken);
|
|
62
|
+
return reply.status(200).send({
|
|
63
|
+
valid: !!session,
|
|
64
|
+
session: session || undefined,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Update device session activity (heartbeat)
|
|
70
|
+
server.put<{
|
|
71
|
+
Body: typeof UpdateDeviceSessionActivityBodySchema._type;
|
|
72
|
+
}>(
|
|
73
|
+
"/activity",
|
|
74
|
+
{
|
|
75
|
+
schema: {
|
|
76
|
+
body: UpdateDeviceSessionActivityBodySchema,
|
|
77
|
+
response: {
|
|
78
|
+
200: Type.Object({
|
|
79
|
+
message: Type.String(),
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
async (request, reply) => {
|
|
85
|
+
const { sessionToken } = request.body as typeof UpdateDeviceSessionActivityBodySchema._type;
|
|
86
|
+
|
|
87
|
+
await updateDeviceSessionActivity(sessionToken);
|
|
88
|
+
return reply.status(200).send({ message: "Device session activity updated" });
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Get device session info (requires device session auth)
|
|
93
|
+
server.get<{
|
|
94
|
+
Reply: typeof DeviceSessionMigrationEligibilityResponseSchema._type;
|
|
95
|
+
}>(
|
|
96
|
+
"/info",
|
|
97
|
+
{
|
|
98
|
+
onRequest: server.requireDeviceSession,
|
|
99
|
+
schema: {
|
|
100
|
+
response: {
|
|
101
|
+
200: DeviceSessionMigrationEligibilityResponseSchema,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
async (request, reply) => {
|
|
106
|
+
const sessionRequest = request as DeviceSessionFastifyRequest;
|
|
107
|
+
const sessionToken = sessionRequest.sessionToken;
|
|
108
|
+
|
|
109
|
+
const eligibility = await validateDeviceSessionMigrationEligibility(sessionToken);
|
|
110
|
+
return reply.status(200).send(eligibility);
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Check migration eligibility
|
|
115
|
+
server.post<{
|
|
116
|
+
Body: typeof ValidateDeviceSessionBodySchema._type;
|
|
117
|
+
Reply: typeof DeviceSessionMigrationEligibilityResponseSchema._type;
|
|
118
|
+
}>(
|
|
119
|
+
"/migration-eligibility",
|
|
120
|
+
{
|
|
121
|
+
schema: {
|
|
122
|
+
body: ValidateDeviceSessionBodySchema,
|
|
123
|
+
response: {
|
|
124
|
+
200: DeviceSessionMigrationEligibilityResponseSchema,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
async (request, reply) => {
|
|
129
|
+
const { sessionToken } = request.body as typeof ValidateDeviceSessionBodySchema._type;
|
|
130
|
+
|
|
131
|
+
const eligibility = await validateDeviceSessionMigrationEligibility(sessionToken);
|
|
132
|
+
return reply.status(200).send(eligibility);
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Delete device session
|
|
137
|
+
server.delete<{
|
|
138
|
+
Body: typeof ValidateDeviceSessionBodySchema._type;
|
|
139
|
+
}>(
|
|
140
|
+
"/",
|
|
141
|
+
{
|
|
142
|
+
schema: {
|
|
143
|
+
body: ValidateDeviceSessionBodySchema,
|
|
144
|
+
response: {
|
|
145
|
+
200: Type.Object({
|
|
146
|
+
message: Type.String(),
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
async (request, reply) => {
|
|
152
|
+
const { sessionToken } = request.body as typeof ValidateDeviceSessionBodySchema._type;
|
|
153
|
+
|
|
154
|
+
await deleteDeviceSession(sessionToken);
|
|
155
|
+
return reply.status(200).send({ message: "Device session deleted successfully" });
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Device session cleanup endpoint (for admin/cron use)
|
|
160
|
+
server.delete<{
|
|
161
|
+
Reply: {
|
|
162
|
+
message: string;
|
|
163
|
+
deletedCount: number;
|
|
164
|
+
};
|
|
165
|
+
}>(
|
|
166
|
+
"/cleanup",
|
|
167
|
+
{
|
|
168
|
+
// Note: In production, this should be protected by admin auth or API key
|
|
169
|
+
schema: {
|
|
170
|
+
response: {
|
|
171
|
+
200: Type.Object({
|
|
172
|
+
message: Type.String(),
|
|
173
|
+
deletedCount: Type.Number(),
|
|
174
|
+
}),
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
async (request, reply) => {
|
|
179
|
+
const deletedCount = await cleanupExpiredDeviceSessions();
|
|
180
|
+
return reply.status(200).send({
|
|
181
|
+
message: `Cleaned up ${deletedCount} expired device sessions`,
|
|
182
|
+
deletedCount,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Health check endpoint
|
|
188
|
+
server.get("/health", async (request, reply) => {
|
|
189
|
+
return reply.code(200).send({
|
|
190
|
+
status: "ok",
|
|
191
|
+
timestamp: new Date().toISOString(),
|
|
192
|
+
service: "device-session"
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export default deviceSessionRoutes;
|