create-authenik8-app 2.4.3 → 2.4.5
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/README.md +151 -4
- package/package.json +10 -8
- package/templates/THREAT_MODEL.md +138 -0
- package/templates/express-auth/README.md +196 -0
- package/templates/express-auth/docker-compose.yml +23 -0
- package/templates/express-auth/package.json +4 -2
- package/templates/express-auth/src/app.ts +1 -1
- package/templates/express-auth/src/server.ts +1 -1
- package/templates/express-auth+/README.md +247 -0
- package/templates/express-auth+/docker-compose.yml +23 -0
- package/templates/express-auth+/package.json +5 -3
- package/templates/express-auth+/src/auth/auth.ts +6 -16
- package/templates/express-auth+/src/auth/controllers/oauth.controller.ts +1 -0
- package/templates/express-auth+/src/auth/{password.controller.ts → controllers/password.controller.ts} +4 -4
- package/templates/express-auth+/src/auth/{protected.controller.ts → controllers/protected.controller.ts} +2 -2
- package/templates/express-auth+/src/auth/{auth.middleware.ts → middleware/auth.middleware.ts} +1 -1
- package/templates/express-auth+/src/auth/routes/oauth.routes.ts +5 -0
- package/templates/express-auth+/src/auth/{password.route.ts → routes/password.route.ts} +1 -1
- package/templates/express-auth+/src/auth/{protected.routes.ts → routes/protected.routes.ts} +2 -2
- package/templates/express-auth+/src/oauth-providers/github/src/auth/auth.ts +42 -0
- package/templates/express-auth+/src/oauth-providers/github/src/auth/oauth.controller.ts +37 -0
- package/templates/express-auth+/src/oauth-providers/github/src/auth/oauth.routes.ts +11 -0
- package/templates/express-auth+/src/oauth-providers/google/src/auth/auth.ts +42 -0
- package/templates/express-auth+/src/oauth-providers/google/src/auth/oauth.controller.ts +37 -0
- package/templates/express-auth+/src/oauth-providers/google/src/auth/oauth.routes.ts +11 -0
- package/templates/express-auth+/src/oauth-providers/google-github/src/auth/auth.ts +47 -0
- package/templates/express-auth+/src/oauth-providers/google-github/src/auth/oauth.controller.ts +57 -0
- package/templates/express-auth+/src/server.ts +3 -3
- package/templates/express-base/README.md +113 -0
- package/templates/express-base/app.ts +1 -1
- package/templates/express-base/docker-compose.yml +23 -0
- package/templates/express-base/package.json +4 -2
- package/templates/express-base/src/package-lock.json +0 -1
- package/templates/express-base/src/server.ts +1 -1
- package/templates/prisma/postgresql/.env.example +11 -0
- package/templates/prisma/sqlite/.env.example +11 -0
- package/templates/express-auth+/src/auth/oauth.controller.ts +0 -38
- 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.
|
|
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": "^
|
|
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
|
|
3
|
-
import {
|
|
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 "
|
|
3
|
-
import { prisma } from "
|
|
4
|
-
import { hashPassword, comparePassword } from "
|
|
5
|
-
import { parseCredentials } from "
|
|
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 "
|
|
2
|
+
import { sanitizeSessionResponse } from "../../utils/security";
|
|
3
3
|
|
|
4
4
|
export const protectedController = {
|
|
5
|
-
protected(
|
|
5
|
+
protected( res: Response) {
|
|
6
6
|
res.json({ message: "Protected route" });
|
|
7
7
|
},
|
|
8
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import express from "express";
|
|
2
|
-
import { adminMiddleware } from "
|
|
3
|
-
import { protectedController } from "
|
|
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;
|
package/templates/express-auth+/src/oauth-providers/google-github/src/auth/oauth.controller.ts
ADDED
|
@@ -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
|
+
};
|