create-authenik8-app 2.4.2 → 2.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +151 -4
  2. package/package.json +11 -9
  3. package/templates/THREAT_MODEL.md +138 -0
  4. package/templates/express-auth/README.md +196 -0
  5. package/templates/express-auth/docker-compose.yml +23 -0
  6. package/templates/express-auth/package.json +4 -2
  7. package/templates/express-auth/src/app.ts +1 -1
  8. package/templates/express-auth/src/server.ts +1 -1
  9. package/templates/express-auth+/README.md +247 -0
  10. package/templates/express-auth+/docker-compose.yml +23 -0
  11. package/templates/express-auth+/package.json +5 -3
  12. package/templates/express-auth+/src/auth/auth.ts +6 -16
  13. package/templates/express-auth+/src/auth/controllers/oauth.controller.ts +1 -0
  14. package/templates/express-auth+/src/auth/{password.controller.ts → controllers/password.controller.ts} +4 -4
  15. package/templates/express-auth+/src/auth/{protected.controller.ts → controllers/protected.controller.ts} +2 -2
  16. package/templates/express-auth+/src/auth/{auth.middleware.ts → middleware/auth.middleware.ts} +1 -1
  17. package/templates/express-auth+/src/auth/routes/oauth.routes.ts +5 -0
  18. package/templates/express-auth+/src/auth/{password.route.ts → routes/password.route.ts} +1 -1
  19. package/templates/express-auth+/src/auth/{protected.routes.ts → routes/protected.routes.ts} +2 -2
  20. package/templates/express-auth+/src/oauth-providers/github/src/auth/auth.ts +42 -0
  21. package/templates/express-auth+/src/oauth-providers/github/src/auth/oauth.controller.ts +37 -0
  22. package/templates/express-auth+/src/oauth-providers/github/src/auth/oauth.routes.ts +11 -0
  23. package/templates/express-auth+/src/oauth-providers/google/src/auth/auth.ts +42 -0
  24. package/templates/express-auth+/src/oauth-providers/google/src/auth/oauth.controller.ts +37 -0
  25. package/templates/express-auth+/src/oauth-providers/google/src/auth/oauth.routes.ts +11 -0
  26. package/templates/express-auth+/src/oauth-providers/google-github/src/auth/auth.ts +47 -0
  27. package/templates/express-auth+/src/oauth-providers/google-github/src/auth/oauth.controller.ts +57 -0
  28. package/templates/express-auth+/src/server.ts +3 -3
  29. package/templates/express-base/README.md +113 -0
  30. package/templates/express-base/app.ts +1 -1
  31. package/templates/express-base/docker-compose.yml +23 -0
  32. package/templates/express-base/package.json +4 -2
  33. package/templates/express-base/src/server.ts +1 -1
  34. package/templates/prisma/postgresql/.env.example +11 -0
  35. package/templates/prisma/sqlite/.env.example +11 -0
  36. package/templates/express-auth+/src/auth/oauth.controller.ts +0 -38
  37. package/templates/express-auth+/src/{auth → oauth-providers/google-github/src/auth}/oauth.routes.ts +1 -1
@@ -0,0 +1,247 @@
1
+ # Authenik8 Express Password + OAuth API
2
+
3
+ Generated by `create-authenik8-app`.
4
+
5
+ ## Start
6
+
7
+ ```bash
8
+ npm install
9
+ npm run docker:up
10
+ npm run prisma:migrate
11
+ npm run dev
12
+ ```
13
+
14
+ For SQLite, Postgres in `docker-compose.yml` is optional. Redis is required for refresh-token rotation and replay protection.
15
+
16
+ ## Environment
17
+
18
+ Review `.env` before running. The generated secrets are development placeholders and must be replaced before deployment.
19
+
20
+ Required:
21
+
22
+ ```bash
23
+ DATABASE_URL=file:./dev.db
24
+ JWT_SECRET=dev-jwt-secret-change-before-production-123456
25
+ REFRESH_SECRET=dev-refresh-secret-change-before-production-123456
26
+ REDIS_HOST=127.0.0.1
27
+ REDIS_PORT=6379
28
+ AUTHENIK8_OAUTH_PROVIDERS=google,github
29
+ ```
30
+
31
+ OAuth provider config is only required for providers listed in `AUTHENIK8_OAUTH_PROVIDERS`.
32
+
33
+ ```bash
34
+ GOOGLE_CLIENT_ID=your-google-client-id
35
+ GOOGLE_CLIENT_SECRET=your-google-client-secret
36
+ GOOGLE_REDIRECT_URI=http://localhost:3000/auth/google/callback
37
+ GITHUB_CLIENT_ID=your-github-client-id
38
+ GITHUB_CLIENT_SECRET=your-github-client-secret
39
+ GITHUB_REDIRECT_URI=http://localhost:3000/auth/github/callback
40
+ ```
41
+
42
+ ## API Contract
43
+
44
+ Register:
45
+
46
+ ```http
47
+ POST /auth/register
48
+ Content-Type: application/json
49
+
50
+ {
51
+ "email": "dev@example.com",
52
+ "password": "password123"
53
+ }
54
+ ```
55
+
56
+ Login:
57
+
58
+ ```http
59
+ POST /auth/login
60
+ Content-Type: application/json
61
+
62
+ {
63
+ "email": "dev@example.com",
64
+ "password": "password123"
65
+ }
66
+ ```
67
+
68
+ Refresh:
69
+
70
+ ```http
71
+ POST /auth/refresh
72
+ Content-Type: application/json
73
+
74
+ {
75
+ "refreshToken": "<refreshToken>"
76
+ }
77
+ ```
78
+
79
+ OAuth:
80
+
81
+ ```http
82
+ GET /auth/google
83
+ GET /auth/google/callback
84
+ GET /auth/github
85
+ GET /auth/github/callback
86
+ GET /auth/google/link
87
+ GET /auth/github/link
88
+ ```
89
+
90
+ Protected:
91
+
92
+ ```http
93
+ GET /protected
94
+ Authorization: Bearer <accessToken>
95
+ ```
96
+
97
+ ## 3-Minute Verification
98
+
99
+ Start the API in one terminal:
100
+
101
+ ```bash
102
+ npm run docker:up
103
+ npm run prisma:migrate
104
+ npm run dev
105
+ ```
106
+
107
+ Register a user:
108
+
109
+ ```bash
110
+ curl -s -X POST http://localhost:3000/auth/register \
111
+ -H "Content-Type: application/json" \
112
+ -d '{"email":"dev@example.com","password":"password123"}'
113
+ ```
114
+
115
+ Login and save the response:
116
+
117
+ ```bash
118
+ curl -s -X POST http://localhost:3000/auth/login \
119
+ -H "Content-Type: application/json" \
120
+ -d '{"email":"dev@example.com","password":"password123"}'
121
+ ```
122
+
123
+ Expected shape:
124
+
125
+ ```json
126
+ {
127
+ "user": {
128
+ "id": "user-id",
129
+ "email": "dev@example.com"
130
+ },
131
+ "accessToken": "access-token",
132
+ "refreshToken": "refresh-token"
133
+ }
134
+ ```
135
+
136
+ Call a protected route:
137
+
138
+ ```bash
139
+ curl http://localhost:3000/protected \
140
+ -H "Authorization: Bearer <accessToken>"
141
+ ```
142
+
143
+ Test OAuth redirect setup in a browser:
144
+
145
+ ```text
146
+ http://localhost:3000/auth/google
147
+ http://localhost:3000/auth/github
148
+ ```
149
+
150
+ If a provider is not listed in `AUTHENIK8_OAUTH_PROVIDERS`, that provider route returns a clear `404` JSON response.
151
+
152
+ ## Environment Variables
153
+
154
+ - `DATABASE_URL`: Prisma database connection. SQLite uses `file:./dev.db`; Postgres uses a `postgresql://...` URL.
155
+ - `JWT_SECRET`: signs short-lived access tokens. Use a long random value in production.
156
+ - `REFRESH_SECRET`: signs refresh tokens. Use a different long random value in production.
157
+ - `REDIS_HOST`: Redis host for refresh-token/session security.
158
+ - `REDIS_PORT`: Redis port, usually `6379` locally.
159
+ - `AUTHENIK8_OAUTH_PROVIDERS`: comma-separated enabled providers, for example `google`, `github`, or `google,github`.
160
+ - `GOOGLE_REDIRECT_URI`: must exactly match the callback URL configured in Google Cloud.
161
+ - `GITHUB_REDIRECT_URI`: must exactly match the callback URL configured in GitHub OAuth Apps.
162
+
163
+ ## Frontend Use
164
+
165
+ Store the access token in memory and use the refresh token only through your chosen secure storage strategy. Add the access token to API requests with the `Authorization` header. For OAuth, redirect the browser to `/auth/google` or `/auth/github`.
166
+
167
+ ```ts
168
+ let accessToken = "";
169
+ let refreshToken = "";
170
+
171
+ export async function login(email: string, password: string) {
172
+ const response = await fetch("http://localhost:3000/auth/login", {
173
+ method: "POST",
174
+ headers: { "Content-Type": "application/json" },
175
+ body: JSON.stringify({ email, password }),
176
+ });
177
+
178
+ if (!response.ok) {
179
+ throw new Error(`Login failed: ${response.status}`);
180
+ }
181
+
182
+ const session = await response.json();
183
+ accessToken = session.accessToken;
184
+ refreshToken = session.refreshToken;
185
+ return session;
186
+ }
187
+
188
+ export async function getProtected() {
189
+ const response = await fetch("http://localhost:3000/protected", {
190
+ headers: { Authorization: `Bearer ${accessToken}` },
191
+ });
192
+
193
+ if (!response.ok) {
194
+ throw new Error(`Protected request failed: ${response.status}`);
195
+ }
196
+
197
+ return response.json();
198
+ }
199
+
200
+ export function loginWithGoogle() {
201
+ window.location.href = "http://localhost:3000/auth/google";
202
+ }
203
+ ```
204
+
205
+ ## OAuth Callback Setup
206
+
207
+ Google callback URL:
208
+
209
+ ```text
210
+ http://localhost:3000/auth/google/callback
211
+ ```
212
+
213
+ GitHub callback URL:
214
+
215
+ ```text
216
+ http://localhost:3000/auth/github/callback
217
+ ```
218
+
219
+ The callback URL in `.env` must match the provider dashboard exactly, including protocol, host, port, and path.
220
+
221
+ ## Troubleshooting
222
+
223
+ `Redis connection refused`: run `npm run docker:up` or start Redis locally with `redis-server --daemonize yes`.
224
+
225
+ `Prisma Client did not initialize`: run `npm run prisma:migrate`, then restart `npm run dev`.
226
+
227
+ `JWT_SECRET must be set to at least 32 characters`: check `.env`; both token secrets must be long strings.
228
+
229
+ `Cannot POST /auth/login`: confirm the server is running and you generated the password or OAuth auth template.
230
+
231
+ `OAuth redirect_uri_mismatch`: copy the callback URL from `.env` into the provider dashboard exactly.
232
+
233
+ `github OAuth is not configured`: add `github` to `AUTHENIK8_OAUTH_PROVIDERS` and set the GitHub env vars, or use only the enabled provider route.
234
+
235
+ `google OAuth is not configured`: add `google` to `AUTHENIK8_OAUTH_PROVIDERS` and set the Google env vars, or use only the enabled provider route.
236
+
237
+ `Port 3000 already in use`: stop the other process or change the `app.listen(3000)` port in `src/server.ts`.
238
+
239
+ `DATABASE_URL is wrong`: for SQLite use `file:./dev.db`; for local Docker Postgres use `postgresql://postgres:postgres@localhost:5432/authenik8?schema=public`.
240
+
241
+ ## Threat Model
242
+
243
+ Read `THREAT_MODEL.md` before deploying. It explains what Authenik8 protects, what Redis-backed token state handles, and what remains your responsibility.
244
+
245
+ ## Deploy
246
+
247
+ Use `npm run build`, run `npx prisma migrate deploy` for production databases, set real secrets in your host, and point Redis/Postgres env vars at managed services.
@@ -0,0 +1,23 @@
1
+ services:
2
+ redis:
3
+ image: redis:7-alpine
4
+ ports:
5
+ - "6379:6379"
6
+ command: ["redis-server", "--appendonly", "yes"]
7
+ volumes:
8
+ - redis-data:/data
9
+
10
+ postgres:
11
+ image: postgres:16-alpine
12
+ ports:
13
+ - "5432:5432"
14
+ environment:
15
+ POSTGRES_USER: postgres
16
+ POSTGRES_PASSWORD: postgres
17
+ POSTGRES_DB: authenik8
18
+ volumes:
19
+ - postgres-data:/var/lib/postgresql/data
20
+
21
+ volumes:
22
+ redis-data:
23
+ postgres-data:
@@ -7,17 +7,19 @@
7
7
  "dev": "ts-node-dev --respawn --transpile-only src/server.ts",
8
8
  "build": "tsc",
9
9
  "start": "node dist/server.js",
10
- "prisma:migrate": "prisma migrate dev"
10
+ "prisma:migrate": "prisma migrate dev",
11
+ "docker:up": "docker compose up -d",
12
+ "docker:down": "docker compose down"
11
13
  },
12
14
  "dependencies": {
13
- "authenik8-core": "^1.0.3",
15
+ "authenik8-core": "^1.0.33",
14
16
  "dotenv": "^16.0.0",
15
17
  "express": "^4.19.2",
16
18
  "@prisma/client": "5.22.0"
17
19
  },
18
20
  "devDependencies": {
19
21
  "@types/express": "^4.17.21",
20
- "@types/node": "^20.0.0",
22
+ "@types/node": "^25.9.1",
21
23
  "ts-node-dev": "^2.0.0",
22
24
  "typescript": "^5.0.0",
23
25
  "prisma": "5.22.0"
@@ -1,30 +1,20 @@
1
1
  import { createAuthenik8 } from "authenik8-core";
2
- import dotenv from "dotenv";
3
- import { requiredEnv, requiredSecret } from "../utils/security";
2
+ import dotenv from "dotenv";
3
+ import { requiredSecret } from "../utils/security";
4
4
 
5
5
  dotenv.config();
6
6
 
7
7
  let authInstance: any;
8
8
 
9
-
9
+ function oauthConfig() {
10
+ return {};
11
+ }
10
12
 
11
13
  export async function initAuth() {
12
14
  authInstance= await createAuthenik8({
13
15
  jwtSecret: requiredSecret("JWT_SECRET"),
14
16
  refreshSecret: requiredSecret("REFRESH_SECRET"),
15
-
16
- oauth: {
17
- google: {
18
- clientId: requiredEnv("GOOGLE_CLIENT_ID"),
19
- clientSecret: requiredEnv("GOOGLE_CLIENT_SECRET"),
20
- redirectUri: requiredEnv("GOOGLE_REDIRECT_URI"),
21
- },
22
- github: {
23
- clientId: requiredEnv("GITHUB_CLIENT_ID"),
24
- clientSecret: requiredEnv("GITHUB_CLIENT_SECRET"),
25
- redirectUri: requiredEnv("GITHUB_REDIRECT_URI"),
26
- },
27
- },
17
+ oauth: oauthConfig(),
28
18
  });
29
19
 
30
20
  }
@@ -0,0 +1 @@
1
+ export const oauthController = {};
@@ -1,8 +1,8 @@
1
1
  import { Request, Response } from "express";
2
- import { getAuth } from "./auth";
3
- import { prisma } from "../prisma/client";
4
- import { hashPassword, comparePassword } from "../utils/hash";
5
- import { parseCredentials } from "../utils/security";
2
+ import { getAuth } from "../auth";
3
+ import { prisma } from "../../prisma/client";
4
+ import { hashPassword, comparePassword } from "../../utils/hash";
5
+ import { parseCredentials } from "../../utils/security";
6
6
 
7
7
  export const passwordController = {
8
8
  async register(req: Request, res: Response) {
@@ -1,8 +1,8 @@
1
1
  import { Request, Response } from "express";
2
- import { sanitizeSessionResponse } from "../utils/security";
2
+ import { sanitizeSessionResponse } from "../../utils/security";
3
3
 
4
4
  export const protectedController = {
5
- protected(req: Request, res: Response) {
5
+ protected( res: Response) {
6
6
  res.json({ message: "Protected route" });
7
7
  },
8
8
 
@@ -1,5 +1,5 @@
1
1
  import { Request, Response, NextFunction } from "express";
2
- import { getAuth } from "./auth";
2
+ import { getAuth } from "../auth";
3
3
 
4
4
  export const adminMiddleware = (req: Request, res: Response, next: NextFunction) => {
5
5
  getAuth().requireAdmin(req, res, next);
@@ -0,0 +1,5 @@
1
+ import express from "express";
2
+
3
+ const router = express.Router();
4
+
5
+ export default router;
@@ -1,5 +1,5 @@
1
1
  import express from "express";
2
- import { passwordController } from "./password.controller";
2
+ import { passwordController } from "../controllers/password.controller";
3
3
 
4
4
  const router = express.Router();
5
5
 
@@ -1,6 +1,6 @@
1
1
  import express from "express";
2
- import { adminMiddleware } from "./auth.middleware";
3
- import { protectedController } from "./protected.controller";
2
+ import { adminMiddleware } from "../middleware/auth.middleware";
3
+ import { protectedController } from "../controllers/protected.controller";
4
4
 
5
5
  const router = express.Router();
6
6
 
@@ -0,0 +1,42 @@
1
+ import { createAuthenik8 } from "authenik8-core";
2
+ import dotenv from "dotenv";
3
+ import { requiredEnv, requiredSecret } from "../../../../src/utils/security";
4
+
5
+ dotenv.config();
6
+
7
+ let authInstance: any;
8
+
9
+ function oauthConfig() {
10
+ return {
11
+ github: {
12
+ clientId: requiredEnv("GITHUB_CLIENT_ID"),
13
+ clientSecret: requiredEnv("GITHUB_CLIENT_SECRET"),
14
+ redirectUri: requiredEnv("GITHUB_REDIRECT_URI"),
15
+ },
16
+ };
17
+ }
18
+
19
+ export async function initAuth() {
20
+ authInstance= await createAuthenik8({
21
+ jwtSecret: requiredSecret("JWT_SECRET"),
22
+ refreshSecret: requiredSecret("REFRESH_SECRET"),
23
+ oauth: oauthConfig(),
24
+ });
25
+
26
+ }
27
+ export function getAuth() {
28
+ if (!authInstance) {
29
+ throw new Error("Auth not initialized. Call initAuth() first.");
30
+ }
31
+
32
+ return authInstance;
33
+ }
34
+
35
+ export const auth = new Proxy(
36
+ {},
37
+ {
38
+ get(_target, property) {
39
+ return getAuth()[property as keyof ReturnType<typeof getAuth>];
40
+ },
41
+ },
42
+ ) as any;
@@ -0,0 +1,37 @@
1
+ import { Request, Response } from "express";
2
+ import { getAuth } from "./auth";
3
+
4
+ type OAuthProvider = "github";
5
+
6
+ function requireProvider(provider: OAuthProvider, res: Response) {
7
+ const oauthProvider = getAuth().oauth?.[provider];
8
+
9
+ if (!oauthProvider) {
10
+ res.status(404).json({ error: `${provider} OAuth is not configured` });
11
+ return undefined;
12
+ }
13
+
14
+ return oauthProvider;
15
+ }
16
+
17
+ export const oauthController = {
18
+ githubRedirect(req: Request, res: Response) {
19
+ requireProvider("github", res)?.redirect(req, res);
20
+ },
21
+
22
+ async githubCallback(req: Request, res: Response) {
23
+ const provider = requireProvider("github", res);
24
+ if (!provider) return;
25
+
26
+ const result = await provider.handleCallback(req);
27
+
28
+ res.json({
29
+ provider: "github",
30
+ ...result,
31
+ });
32
+ },
33
+
34
+ githubLink(req: Request, res: Response) {
35
+ requireProvider("github", res)?.redirect(req, res, "link");
36
+ },
37
+ };
@@ -0,0 +1,11 @@
1
+ import express from "express";
2
+ import { authMiddleware } from "./auth.middleware";
3
+ import { oauthController } from "./oauth.controller";
4
+
5
+ const router = express.Router();
6
+
7
+ router.get("/github", oauthController.githubRedirect);
8
+ router.get("/github/callback", oauthController.githubCallback);
9
+ router.get("/github/link", authMiddleware, oauthController.githubLink);
10
+
11
+ export default router;
@@ -0,0 +1,42 @@
1
+ import { createAuthenik8 } from "authenik8-core";
2
+ import dotenv from "dotenv";
3
+ import { requiredEnv, requiredSecret } from "../../../../src/utils/security";
4
+
5
+ dotenv.config();
6
+
7
+ let authInstance: any;
8
+
9
+ function oauthConfig() {
10
+ return {
11
+ google: {
12
+ clientId: requiredEnv("GOOGLE_CLIENT_ID"),
13
+ clientSecret: requiredEnv("GOOGLE_CLIENT_SECRET"),
14
+ redirectUri: requiredEnv("GOOGLE_REDIRECT_URI"),
15
+ },
16
+ };
17
+ }
18
+
19
+ export async function initAuth() {
20
+ authInstance= await createAuthenik8({
21
+ jwtSecret: requiredSecret("JWT_SECRET"),
22
+ refreshSecret: requiredSecret("REFRESH_SECRET"),
23
+ oauth: oauthConfig(),
24
+ });
25
+
26
+ }
27
+ export function getAuth() {
28
+ if (!authInstance) {
29
+ throw new Error("Auth not initialized. Call initAuth() first.");
30
+ }
31
+
32
+ return authInstance;
33
+ }
34
+
35
+ export const auth = new Proxy(
36
+ {},
37
+ {
38
+ get(_target, property) {
39
+ return getAuth()[property as keyof ReturnType<typeof getAuth>];
40
+ },
41
+ },
42
+ ) as any;
@@ -0,0 +1,37 @@
1
+ import { Request, Response } from "express";
2
+ import { getAuth } from "./auth";
3
+
4
+ type OAuthProvider = "google";
5
+
6
+ function requireProvider(provider: OAuthProvider, res: Response) {
7
+ const oauthProvider = getAuth().oauth?.[provider];
8
+
9
+ if (!oauthProvider) {
10
+ res.status(404).json({ error: `${provider} OAuth is not configured` });
11
+ return undefined;
12
+ }
13
+
14
+ return oauthProvider;
15
+ }
16
+
17
+ export const oauthController = {
18
+ googleRedirect(req: Request, res: Response) {
19
+ requireProvider("google", res)?.redirect(req, res);
20
+ },
21
+
22
+ async googleCallback(req: Request, res: Response) {
23
+ const provider = requireProvider("google", res);
24
+ if (!provider) return;
25
+
26
+ const result = await provider.handleCallback(req);
27
+
28
+ res.json({
29
+ provider: "google",
30
+ ...result,
31
+ });
32
+ },
33
+
34
+ googleLink(req: Request, res: Response) {
35
+ requireProvider("google", res)?.redirect(req, res, "link");
36
+ },
37
+ };
@@ -0,0 +1,11 @@
1
+ import express from "express";
2
+ import { authMiddleware } from "./auth.middleware";
3
+ import { oauthController } from "./oauth.controller";
4
+
5
+ const router = express.Router();
6
+
7
+ router.get("/google", oauthController.googleRedirect);
8
+ router.get("/google/callback", oauthController.googleCallback);
9
+ router.get("/google/link", authMiddleware, oauthController.googleLink);
10
+
11
+ export default router;
@@ -0,0 +1,47 @@
1
+ import { createAuthenik8 } from "authenik8-core";
2
+ import dotenv from "dotenv";
3
+ import { requiredEnv, requiredSecret } from "../utils/security";
4
+
5
+ dotenv.config();
6
+
7
+ let authInstance: any;
8
+
9
+ function oauthConfig() {
10
+ return {
11
+ google: {
12
+ clientId: requiredEnv("GOOGLE_CLIENT_ID"),
13
+ clientSecret: requiredEnv("GOOGLE_CLIENT_SECRET"),
14
+ redirectUri: requiredEnv("GOOGLE_REDIRECT_URI"),
15
+ },
16
+ github: {
17
+ clientId: requiredEnv("GITHUB_CLIENT_ID"),
18
+ clientSecret: requiredEnv("GITHUB_CLIENT_SECRET"),
19
+ redirectUri: requiredEnv("GITHUB_REDIRECT_URI"),
20
+ },
21
+ };
22
+ }
23
+
24
+ export async function initAuth() {
25
+ authInstance= await createAuthenik8({
26
+ jwtSecret: requiredSecret("JWT_SECRET"),
27
+ refreshSecret: requiredSecret("REFRESH_SECRET"),
28
+ oauth: oauthConfig(),
29
+ });
30
+
31
+ }
32
+ export function getAuth() {
33
+ if (!authInstance) {
34
+ throw new Error("Auth not initialized. Call initAuth() first.");
35
+ }
36
+
37
+ return authInstance;
38
+ }
39
+
40
+ export const auth = new Proxy(
41
+ {},
42
+ {
43
+ get(_target, property) {
44
+ return getAuth()[property as keyof ReturnType<typeof getAuth>];
45
+ },
46
+ },
47
+ ) as any;
@@ -0,0 +1,57 @@
1
+ import { Request, Response } from "express";
2
+ import { getAuth } from "./auth";
3
+
4
+ type OAuthProvider = "google" | "github";
5
+
6
+ function requireProvider(provider: OAuthProvider, res: Response) {
7
+ const oauthProvider = getAuth().oauth?.[provider];
8
+
9
+ if (!oauthProvider) {
10
+ res.status(404).json({ error: `${provider} OAuth is not configured` });
11
+ return undefined;
12
+ }
13
+
14
+ return oauthProvider;
15
+ }
16
+
17
+ export const oauthController = {
18
+ googleRedirect(req: Request, res: Response) {
19
+ requireProvider("google", res)?.redirect(req, res);
20
+ },
21
+
22
+ async googleCallback(req: Request, res: Response) {
23
+ const provider = requireProvider("google", res);
24
+ if (!provider) return;
25
+
26
+ const result = await provider.handleCallback(req);
27
+
28
+ res.json({
29
+ provider: "google",
30
+ ...result,
31
+ });
32
+ },
33
+
34
+ googleLink(req: Request, res: Response) {
35
+ requireProvider("google", res)?.redirect(req, res, "link");
36
+ },
37
+
38
+ githubRedirect(req: Request, res: Response) {
39
+ requireProvider("github", res)?.redirect(req, res);
40
+ },
41
+
42
+ async githubCallback(req: Request, res: Response) {
43
+ const provider = requireProvider("github", res);
44
+ if (!provider) return;
45
+
46
+ const result = await provider.handleCallback(req);
47
+
48
+ res.json({
49
+ provider: "github",
50
+ ...result,
51
+ });
52
+ },
53
+
54
+ githubLink(req: Request, res: Response) {
55
+ requireProvider("github", res)?.redirect(req, res, "link");
56
+ },
57
+ };