@owox/idp-better-auth 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +183 -0
- package/dist/adapters/database.d.ts +29 -0
- package/dist/adapters/database.d.ts.map +1 -0
- package/dist/adapters/database.js +85 -0
- package/dist/auth/auth-config.d.ts +4 -0
- package/dist/auth/auth-config.d.ts.map +1 -0
- package/dist/auth/auth-config.js +72 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/providers/better-auth-provider.d.ts +34 -0
- package/dist/providers/better-auth-provider.d.ts.map +1 -0
- package/dist/providers/better-auth-provider.js +99 -0
- package/dist/services/authentication-service.d.ts +23 -0
- package/dist/services/authentication-service.d.ts.map +1 -0
- package/dist/services/authentication-service.js +199 -0
- package/dist/services/crypto-service.d.ts +18 -0
- package/dist/services/crypto-service.d.ts.map +1 -0
- package/dist/services/crypto-service.js +100 -0
- package/dist/services/database-service.d.ts +14 -0
- package/dist/services/database-service.d.ts.map +1 -0
- package/dist/services/database-service.js +142 -0
- package/dist/services/magic-link-service.d.ts +10 -0
- package/dist/services/magic-link-service.d.ts.map +1 -0
- package/dist/services/magic-link-service.js +42 -0
- package/dist/services/middleware-service.d.ts +15 -0
- package/dist/services/middleware-service.d.ts.map +1 -0
- package/dist/services/middleware-service.js +38 -0
- package/dist/services/page-service.d.ts +23 -0
- package/dist/services/page-service.d.ts.map +1 -0
- package/dist/services/page-service.js +331 -0
- package/dist/services/request-handler-service.d.ts +10 -0
- package/dist/services/request-handler-service.d.ts.map +1 -0
- package/dist/services/request-handler-service.js +50 -0
- package/dist/services/template-service.d.ts +28 -0
- package/dist/services/template-service.d.ts.map +1 -0
- package/dist/services/template-service.js +198 -0
- package/dist/services/token-service.d.ts +15 -0
- package/dist/services/token-service.d.ts.map +1 -0
- package/dist/services/token-service.js +108 -0
- package/dist/services/user-management-service.d.ts +74 -0
- package/dist/services/user-management-service.d.ts.map +1 -0
- package/dist/services/user-management-service.js +396 -0
- package/dist/templates/admin-add-user.html +298 -0
- package/dist/templates/admin-dashboard.html +214 -0
- package/dist/templates/admin-user-details.html +278 -0
- package/dist/templates/password-setup.html +267 -0
- package/dist/templates/password-success.html +197 -0
- package/dist/templates/sign-in.html +201 -0
- package/dist/types/auth-session.d.ts +25 -0
- package/dist/types/auth-session.d.ts.map +1 -0
- package/dist/types/auth-session.js +1 -0
- package/dist/types/database-models.d.ts +54 -0
- package/dist/types/database-models.d.ts.map +1 -0
- package/dist/types/database-models.js +1 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/package.json +69 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { TemplateService } from './template-service.js';
|
|
2
|
+
export class PageService {
|
|
3
|
+
authenticationService;
|
|
4
|
+
userManagementService;
|
|
5
|
+
cryptoService;
|
|
6
|
+
constructor(authenticationService, userManagementService, cryptoService) {
|
|
7
|
+
this.authenticationService = authenticationService;
|
|
8
|
+
this.userManagementService = userManagementService;
|
|
9
|
+
this.cryptoService = cryptoService;
|
|
10
|
+
}
|
|
11
|
+
async signInPage(req, res) {
|
|
12
|
+
try {
|
|
13
|
+
const session = await this.authenticationService.getSession(req);
|
|
14
|
+
if (session) {
|
|
15
|
+
res.redirect('/');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.error('Error checking authentication for sign-in page:', error);
|
|
21
|
+
res.send(TemplateService.renderSignIn());
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
res.send(TemplateService.renderSignIn());
|
|
25
|
+
}
|
|
26
|
+
async setupPasswordPage(req, res) {
|
|
27
|
+
try {
|
|
28
|
+
const session = await this.authenticationService.getSession(req);
|
|
29
|
+
if (!session || !session.user) {
|
|
30
|
+
res.redirect('/auth/sign-in?error=Invalid or expired magic link');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
res.send(TemplateService.renderPasswordSetup());
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error('Error loading password setup page:', error);
|
|
37
|
+
res.redirect('/auth/sign-in');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async setPassword(req, res) {
|
|
41
|
+
const { password, confirmPassword } = req.body;
|
|
42
|
+
if (!password || password !== confirmPassword) {
|
|
43
|
+
res.status(400).send('Passwords do not match');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const session = await this.authenticationService.getSession(req);
|
|
48
|
+
if (!session || !session.user) {
|
|
49
|
+
res.redirect('/auth/sign-in');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
await this.authenticationService.setPassword(password, req);
|
|
54
|
+
//TODO: this is not working
|
|
55
|
+
res.setHeader('Set-Cookie', `refreshToken=${session.session.token}; Path=/; HttpOnly; SameSite=Strict`);
|
|
56
|
+
res.send(TemplateService.renderPasswordSuccess());
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error instanceof Error && error.message === 'User already has a password') {
|
|
60
|
+
res.status(400).send('User already has a password');
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
console.error('Failed to set password:', error);
|
|
64
|
+
res.status(500).send('Failed to set password. Please try again.');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error('Password update failed:', error);
|
|
70
|
+
res.status(500).send('Failed to set password');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async magicLinkSuccess(req, res) {
|
|
74
|
+
try {
|
|
75
|
+
if (req.query.error) {
|
|
76
|
+
res.redirect(`/auth/sign-in?error=Magic link verification failed: ${req.query.error}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const session = await this.authenticationService.getSession(req);
|
|
80
|
+
if (!session || !session.user || !req.query.role) {
|
|
81
|
+
res.redirect('/auth/sign-in?error=Invalid magic link');
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
let role;
|
|
85
|
+
try {
|
|
86
|
+
role = await this.cryptoService.decrypt(req.query.role);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
console.error('Failed to decrypt role:', error);
|
|
90
|
+
res.redirect('/auth/sign-in?error=Invalid magic link');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (this.userManagementService) {
|
|
94
|
+
try {
|
|
95
|
+
if (session?.user?.id) {
|
|
96
|
+
if (!session.user.name && session.user.email) {
|
|
97
|
+
const generatedName = this.generateNameFromEmail(session.user.email);
|
|
98
|
+
await this.userManagementService.updateUserName(session.user.id, generatedName);
|
|
99
|
+
}
|
|
100
|
+
await this.userManagementService.addMemberToOrganization(req, role);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Failed to ensure user in organization after magic link success:', error);
|
|
105
|
+
throw new Error(`Failed to ensure user in organization after magic link success: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
res.redirect('/auth/setup-password');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error('Magic link success handler failed:', error);
|
|
112
|
+
res.redirect('/auth/sign-in?error=Something went wrong');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
registerRoutes(express) {
|
|
116
|
+
try {
|
|
117
|
+
express.get('/auth/setup-password', this.setupPasswordPage.bind(this));
|
|
118
|
+
express.post('/auth/set-password', this.setPassword.bind(this));
|
|
119
|
+
express.get('/auth/magic-link-success', this.magicLinkSuccess.bind(this));
|
|
120
|
+
express.get('/auth', (req, res) => {
|
|
121
|
+
res.redirect('/auth/dashboard');
|
|
122
|
+
});
|
|
123
|
+
express.get('/auth/dashboard', this.authenticationService.requireAuthMiddleware.bind(this.authenticationService), this.adminDashboard.bind(this));
|
|
124
|
+
express.get('/auth/user/:id', this.authenticationService.requireAuthMiddleware.bind(this.authenticationService), this.adminUserDetails.bind(this));
|
|
125
|
+
express.get('/auth/add-user', this.authenticationService.requireAuthMiddleware.bind(this.authenticationService), this.adminAddUserPage.bind(this));
|
|
126
|
+
express.post('/auth/add-user', this.authenticationService.requireAuthMiddleware.bind(this.authenticationService), this.adminAddUser.bind(this));
|
|
127
|
+
express.post('/auth/delete-user/:id', this.authenticationService.requireAuthMiddleware.bind(this.authenticationService), this.adminDeleteUser.bind(this));
|
|
128
|
+
express.post('/auth/generate-magic-link', this.authenticationService.requireAuthMiddleware.bind(this.authenticationService), this.adminGenerateMagicLink.bind(this));
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error('Failed to register page routes:', error);
|
|
132
|
+
throw new Error('Failed to register page routes');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async adminDashboard(req, res) {
|
|
136
|
+
try {
|
|
137
|
+
const session = await this.authenticationService.getSession(req);
|
|
138
|
+
const currentUserEmail = session?.user?.email || 'Unknown User';
|
|
139
|
+
const users = await this.userManagementService.getUsersForAdmin();
|
|
140
|
+
const html = TemplateService.renderAdminDashboard(users, currentUserEmail);
|
|
141
|
+
res.send(html);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error('Error loading admin dashboard:', error);
|
|
145
|
+
res.status(500).send('Error loading dashboard');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async adminUserDetails(req, res) {
|
|
149
|
+
try {
|
|
150
|
+
const userId = req.params.id;
|
|
151
|
+
if (!userId) {
|
|
152
|
+
res.status(400).send('User ID is required');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const user = await this.userManagementService.getUserDetails(userId);
|
|
156
|
+
if (!user) {
|
|
157
|
+
res.status(404).send('User not found');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// Get current user role to determine if delete button should be shown
|
|
161
|
+
const session = await this.authenticationService.getSession(req);
|
|
162
|
+
const currentUserRole = session?.user?.id
|
|
163
|
+
? await this.userManagementService.getUserRole(session.user.id)
|
|
164
|
+
: null;
|
|
165
|
+
const html = TemplateService.renderUserDetails(user, currentUserRole);
|
|
166
|
+
res.send(html);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
console.error('Error loading user details:', error);
|
|
170
|
+
res.status(500).send('Error loading user details');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async adminAddUserPage(req, res) {
|
|
174
|
+
try {
|
|
175
|
+
// Get current user role to determine available roles for invitation
|
|
176
|
+
const session = await this.authenticationService.getSession(req);
|
|
177
|
+
if (!session?.user?.id) {
|
|
178
|
+
res.redirect('/auth/sign-in');
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const currentUserRole = await this.userManagementService.getUserRole(session.user.id);
|
|
182
|
+
if (!currentUserRole) {
|
|
183
|
+
res.status(403).send('Access denied');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (!this.userManagementService.isValidRole(currentUserRole)) {
|
|
187
|
+
res.status(403).send('Access denied');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const allowedRoles = this.userManagementService.getAllowedRolesForInvite(currentUserRole);
|
|
191
|
+
const html = TemplateService.renderAddUser(allowedRoles);
|
|
192
|
+
res.send(html);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
console.error('Error loading add user page:', error);
|
|
196
|
+
res.status(500).send('Error loading page');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async adminAddUser(req, res) {
|
|
200
|
+
try {
|
|
201
|
+
const { email, role } = req.body;
|
|
202
|
+
if (!email || !role) {
|
|
203
|
+
res.status(400).json({ error: 'Email and role are required' });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Validate role
|
|
207
|
+
if (!this.userManagementService.isValidRole(role)) {
|
|
208
|
+
res.status(400).json({ error: 'Invalid role' });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// Get current user role and check permissions
|
|
212
|
+
const session = await this.authenticationService.getSession(req);
|
|
213
|
+
if (!session?.user?.id) {
|
|
214
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const currentUserRole = await this.userManagementService.getUserRole(session.user.id);
|
|
218
|
+
if (!currentUserRole || !this.userManagementService.isValidRole(currentUserRole)) {
|
|
219
|
+
res.status(403).json({ error: 'Access denied' });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// Check if current user can invite target role
|
|
223
|
+
if (!this.userManagementService.canInviteRole(currentUserRole, role)) {
|
|
224
|
+
res.status(403).json({
|
|
225
|
+
error: `You don't have permission to invite users with role: ${role}`,
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const magicLink = await this.userManagementService.generateMagicLinkForUser(email, role);
|
|
230
|
+
res.json({
|
|
231
|
+
success: true,
|
|
232
|
+
magicLink,
|
|
233
|
+
email,
|
|
234
|
+
role,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error('Error adding user:', error);
|
|
239
|
+
res.status(500).json({ error: 'Failed to generate magic link' });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async adminDeleteUser(req, res) {
|
|
243
|
+
try {
|
|
244
|
+
const userId = req.params.id;
|
|
245
|
+
if (!userId) {
|
|
246
|
+
res.status(400).json({ error: 'User ID is required' });
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// Check if current user has admin role
|
|
250
|
+
const session = await this.authenticationService.getSession(req);
|
|
251
|
+
if (!session || !session.user) {
|
|
252
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
const currentUserRole = await this.userManagementService.getUserRole(session.user.id);
|
|
256
|
+
if (currentUserRole !== 'admin') {
|
|
257
|
+
res.status(403).json({ error: 'Only admins can delete users' });
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
await this.userManagementService.removeUser(userId);
|
|
261
|
+
res.json({ success: true });
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
console.error('Error deleting user:', error);
|
|
265
|
+
res.status(500).json({ error: 'Failed to delete user' });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async adminGenerateMagicLink(req, res) {
|
|
269
|
+
try {
|
|
270
|
+
const { userId, email, role } = req.body || {};
|
|
271
|
+
if (!email || !role) {
|
|
272
|
+
res.status(400).json({ error: 'Email and role are required' });
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Validate role
|
|
276
|
+
if (!this.userManagementService.isValidRole(role)) {
|
|
277
|
+
res.status(400).json({ error: 'Invalid role' });
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// Get current user role and check permissions
|
|
281
|
+
const session = await this.authenticationService.getSession(req);
|
|
282
|
+
if (!session?.user?.id) {
|
|
283
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const currentUserRole = await this.userManagementService.getUserRole(session.user.id);
|
|
287
|
+
if (!currentUserRole || !this.userManagementService.isValidRole(currentUserRole)) {
|
|
288
|
+
res.status(403).json({ error: 'Access denied' });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Check if current user can invite target role
|
|
292
|
+
if (!this.userManagementService.canInviteRole(currentUserRole, role)) {
|
|
293
|
+
res.status(403).json({
|
|
294
|
+
error: `You don't have permission to generate magic link for role: ${role}`,
|
|
295
|
+
});
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const magicLink = await this.userManagementService.generateMagicLinkForUser(email, role);
|
|
299
|
+
res.json({
|
|
300
|
+
success: true,
|
|
301
|
+
magicLink,
|
|
302
|
+
userId,
|
|
303
|
+
email,
|
|
304
|
+
role,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
console.error('Error generating magic link:', error);
|
|
309
|
+
res.status(500).json({ error: 'Failed to generate magic link' });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
generateNameFromEmail(email) {
|
|
313
|
+
try {
|
|
314
|
+
const parts = email.split('@');
|
|
315
|
+
const localPart = parts[0];
|
|
316
|
+
if (!localPart) {
|
|
317
|
+
return email; // Fallback to full email
|
|
318
|
+
}
|
|
319
|
+
// Convert dots and underscores to spaces and capitalize
|
|
320
|
+
return localPart
|
|
321
|
+
.replace(/[._]/g, ' ')
|
|
322
|
+
.split(' ')
|
|
323
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
324
|
+
.join(' ');
|
|
325
|
+
}
|
|
326
|
+
catch (error) {
|
|
327
|
+
console.error('Error generating name from email:', error);
|
|
328
|
+
return email; // Fallback to full email
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createBetterAuthConfig } from '../auth/auth-config.js';
|
|
2
|
+
import { type Express, type Request as ExpressRequest } from 'express';
|
|
3
|
+
export declare class RequestHandlerService {
|
|
4
|
+
private readonly auth;
|
|
5
|
+
private static readonly AUTH_ROUTE_PREFIX;
|
|
6
|
+
constructor(auth: Awaited<ReturnType<typeof createBetterAuthConfig>>);
|
|
7
|
+
setupBetterAuthHandler(expressApp: Express): void;
|
|
8
|
+
convertExpressToFetchRequest(req: ExpressRequest): Request;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=request-handler-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-handler-service.d.ts","sourceRoot":"","sources":["../../src/services/request-handler-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,OAAO,IAAI,cAAc,EAG/B,MAAM,SAAS,CAAC;AAEjB,qBAAa,qBAAqB;IAGpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAuB;gBAEnC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC;IAErF,sBAAsB,CAAC,UAAU,EAAE,OAAO,GAAG,IAAI;IAwBjD,4BAA4B,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO;CAwB3D"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export class RequestHandlerService {
|
|
2
|
+
auth;
|
|
3
|
+
static AUTH_ROUTE_PREFIX = '/auth/better-auth';
|
|
4
|
+
constructor(auth) {
|
|
5
|
+
this.auth = auth;
|
|
6
|
+
}
|
|
7
|
+
setupBetterAuthHandler(expressApp) {
|
|
8
|
+
expressApp.use(async (req, res, next) => {
|
|
9
|
+
if (!req.path.startsWith(RequestHandlerService.AUTH_ROUTE_PREFIX)) {
|
|
10
|
+
return next();
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
const fetchRequest = this.convertExpressToFetchRequest(req);
|
|
14
|
+
const response = await this.auth.handler(fetchRequest);
|
|
15
|
+
response.headers.forEach((value, key) => {
|
|
16
|
+
res.set(key, value);
|
|
17
|
+
});
|
|
18
|
+
res.status(response.status);
|
|
19
|
+
const body = await response.text();
|
|
20
|
+
res.send(body);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error('Auth handler error:', error);
|
|
24
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
convertExpressToFetchRequest(req) {
|
|
29
|
+
try {
|
|
30
|
+
const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`;
|
|
31
|
+
const method = req.method;
|
|
32
|
+
const headers = new Headers();
|
|
33
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
34
|
+
if (typeof value === 'string') {
|
|
35
|
+
headers.set(key, value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const fetchRequest = new Request(url, {
|
|
39
|
+
method,
|
|
40
|
+
headers,
|
|
41
|
+
body: method !== 'GET' && method !== 'HEAD' ? JSON.stringify(req.body) : undefined,
|
|
42
|
+
});
|
|
43
|
+
return fetchRequest;
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('Failed to convert Express request to Fetch request:', error);
|
|
47
|
+
throw new Error('Failed to convert request format');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare class TemplateService {
|
|
2
|
+
private static getTemplatePath;
|
|
3
|
+
static loadTemplate(templateName: string): string;
|
|
4
|
+
private static getOwoxTailwindConfig;
|
|
5
|
+
static renderSignIn(): string;
|
|
6
|
+
static renderPasswordSetup(): string;
|
|
7
|
+
static renderPasswordSuccess(): string;
|
|
8
|
+
static renderAdminDashboard(users: Array<{
|
|
9
|
+
id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
name: string | null;
|
|
12
|
+
role: string;
|
|
13
|
+
createdAt: string;
|
|
14
|
+
updatedAt: string | null;
|
|
15
|
+
}>, currentUserEmail: string): string;
|
|
16
|
+
static renderUserDetails(user: {
|
|
17
|
+
id: string;
|
|
18
|
+
email: string;
|
|
19
|
+
name: string | null;
|
|
20
|
+
role: string;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
updatedAt: string | null;
|
|
23
|
+
organizationId: string | null;
|
|
24
|
+
}, currentUserRole?: string | null): string;
|
|
25
|
+
static renderAddUser(allowedRoles?: string[]): string;
|
|
26
|
+
private static formatDate;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=template-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-service.d.ts","sourceRoot":"","sources":["../../src/services/template-service.ts"],"names":[],"mappings":"AAKA,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAC,eAAe;WAYhB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAKxD,OAAO,CAAC,MAAM,CAAC,qBAAqB;WAyDtB,YAAY,IAAI,MAAM;WAItB,mBAAmB,IAAI,MAAM;WAI7B,qBAAqB,IAAI,MAAM;WAI/B,oBAAoB,CAChC,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC,EACF,gBAAgB,EAAE,MAAM,GACvB,MAAM;WAoEK,iBAAiB,CAC7B,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;KAC/B,EACD,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAC9B,MAAM;WAiCK,aAAa,CAAC,YAAY,GAAE,MAAM,EAAkC,GAAG,MAAM;IAa3F,OAAO,CAAC,MAAM,CAAC,UAAU;CAc1B"}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
export class TemplateService {
|
|
6
|
+
static getTemplatePath(templateName) {
|
|
7
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const distPath = join(currentDir, '..', 'templates', templateName);
|
|
9
|
+
if (existsSync(distPath)) {
|
|
10
|
+
return distPath;
|
|
11
|
+
}
|
|
12
|
+
const srcPath = join(currentDir, '..', '..', 'src', 'templates', templateName);
|
|
13
|
+
return srcPath;
|
|
14
|
+
}
|
|
15
|
+
static loadTemplate(templateName) {
|
|
16
|
+
const templatePath = this.getTemplatePath(templateName);
|
|
17
|
+
return readFileSync(templatePath, 'utf-8');
|
|
18
|
+
}
|
|
19
|
+
static getOwoxTailwindConfig() {
|
|
20
|
+
return `
|
|
21
|
+
tailwind.config = {
|
|
22
|
+
theme: {
|
|
23
|
+
extend: {
|
|
24
|
+
colors: {
|
|
25
|
+
// OWOX Design System Colors
|
|
26
|
+
background: "oklch(1 0 0)",
|
|
27
|
+
foreground: "oklch(0.3346 0.0123 279.25)",
|
|
28
|
+
card: "oklch(1 0 0)",
|
|
29
|
+
'card-foreground': "oklch(0.3346 0.0123 279.25)",
|
|
30
|
+
primary: {
|
|
31
|
+
DEFAULT: "oklch(0.6179 0.2295 250.87)",
|
|
32
|
+
hover: "oklch(0.54 0.23 250.87)",
|
|
33
|
+
foreground: "oklch(0.985 0 0)",
|
|
34
|
+
},
|
|
35
|
+
secondary: {
|
|
36
|
+
DEFAULT: "oklch(0.97 0 0)",
|
|
37
|
+
foreground: "oklch(0.205 0 0)",
|
|
38
|
+
},
|
|
39
|
+
muted: {
|
|
40
|
+
DEFAULT: "oklch(0.97 0 0)",
|
|
41
|
+
foreground: "oklch(0.5148 0.0128 274.72)",
|
|
42
|
+
},
|
|
43
|
+
accent: {
|
|
44
|
+
DEFAULT: "oklch(0.97 0 0)",
|
|
45
|
+
foreground: "oklch(0.205 0 0)",
|
|
46
|
+
},
|
|
47
|
+
destructive: {
|
|
48
|
+
DEFAULT: "oklch(0.577 0.245 27.325)",
|
|
49
|
+
foreground: "oklch(0.985 0 0)",
|
|
50
|
+
},
|
|
51
|
+
border: "oklch(0.922 0 0)",
|
|
52
|
+
input: "oklch(0.922 0 0)",
|
|
53
|
+
ring: "oklch(0.708 0 0)",
|
|
54
|
+
success: {
|
|
55
|
+
DEFAULT: "oklch(0.647 0.165 142.495)",
|
|
56
|
+
foreground: "oklch(0.985 0 0)",
|
|
57
|
+
},
|
|
58
|
+
'brand-blue': {
|
|
59
|
+
50: "oklch(0.985 0.03 250.87)",
|
|
60
|
+
100: "oklch(0.955 0.05 250.87)",
|
|
61
|
+
200: "oklch(0.9 0.08 250.87)",
|
|
62
|
+
300: "oklch(0.8 0.14 250.87)",
|
|
63
|
+
400: "oklch(0.7 0.19 250.87)",
|
|
64
|
+
500: "oklch(0.6179 0.2295 250.87)",
|
|
65
|
+
600: "oklch(0.54 0.23 250.87)",
|
|
66
|
+
700: "oklch(0.44 0.2 250.87)",
|
|
67
|
+
800: "oklch(0.36 0.17 250.87)",
|
|
68
|
+
900: "oklch(0.28 0.14 250.87)",
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
}`;
|
|
74
|
+
}
|
|
75
|
+
static renderSignIn() {
|
|
76
|
+
return this.loadTemplate('sign-in.html');
|
|
77
|
+
}
|
|
78
|
+
static renderPasswordSetup() {
|
|
79
|
+
return this.loadTemplate('password-setup.html');
|
|
80
|
+
}
|
|
81
|
+
static renderPasswordSuccess() {
|
|
82
|
+
return this.loadTemplate('password-success.html');
|
|
83
|
+
}
|
|
84
|
+
static renderAdminDashboard(users, currentUserEmail) {
|
|
85
|
+
const template = this.loadTemplate('admin-dashboard.html');
|
|
86
|
+
// Calculate stats
|
|
87
|
+
const totalUsers = users.length;
|
|
88
|
+
const activeUsers = users.length; // For now, all users are considered active
|
|
89
|
+
const adminUsers = users.filter(u => u.role === 'admin').length;
|
|
90
|
+
// Generate user rows
|
|
91
|
+
const userRows = users
|
|
92
|
+
.map(user => {
|
|
93
|
+
const getRoleBadgeClasses = (role) => {
|
|
94
|
+
switch (role) {
|
|
95
|
+
case 'admin':
|
|
96
|
+
return 'bg-primary text-primary-foreground';
|
|
97
|
+
case 'editor':
|
|
98
|
+
return 'bg-success text-success-foreground';
|
|
99
|
+
case 'viewer':
|
|
100
|
+
return 'bg-muted text-muted-foreground';
|
|
101
|
+
default:
|
|
102
|
+
return 'bg-muted text-muted-foreground';
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const roleBadgeClasses = getRoleBadgeClasses(user.role);
|
|
106
|
+
const formattedCreatedAt = this.formatDate(user.createdAt);
|
|
107
|
+
const formattedUpdatedAt = user.updatedAt ? this.formatDate(user.updatedAt) : 'Never';
|
|
108
|
+
return `
|
|
109
|
+
<tr class="table-row">
|
|
110
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
111
|
+
<div class="flex items-center">
|
|
112
|
+
<div class="flex-shrink-0 h-10 w-10">
|
|
113
|
+
<div class="h-10 w-10 rounded-full bg-muted flex items-center justify-center">
|
|
114
|
+
<svg class="h-5 w-5 text-muted-foreground" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
115
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
|
|
116
|
+
</svg>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="ml-4">
|
|
120
|
+
<div class="text-sm font-medium text-foreground">${user.name || 'No name'}</div>
|
|
121
|
+
<div class="text-sm text-muted-foreground">${user.email}</div>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</td>
|
|
125
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
126
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${roleBadgeClasses}">
|
|
127
|
+
${user.role}
|
|
128
|
+
</span>
|
|
129
|
+
</td>
|
|
130
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-foreground">${formattedCreatedAt}</td>
|
|
131
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-foreground">${formattedUpdatedAt}</td>
|
|
132
|
+
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
133
|
+
<a href="/auth/user/${user.id}" class="text-primary hover:text-primary/80 font-medium transition-colors">View</a>
|
|
134
|
+
</td>
|
|
135
|
+
</tr>
|
|
136
|
+
`;
|
|
137
|
+
})
|
|
138
|
+
.join('');
|
|
139
|
+
return template
|
|
140
|
+
.replace('{{TOTAL_USERS}}', totalUsers.toString())
|
|
141
|
+
.replace('{{ACTIVE_USERS}}', activeUsers.toString())
|
|
142
|
+
.replace('{{ADMIN_USERS}}', adminUsers.toString())
|
|
143
|
+
.replace('{{USERS_ROWS}}', userRows)
|
|
144
|
+
.replace('{{CURRENT_USER_EMAIL}}', currentUserEmail);
|
|
145
|
+
}
|
|
146
|
+
static renderUserDetails(user, currentUserRole) {
|
|
147
|
+
const template = this.loadTemplate('admin-user-details.html');
|
|
148
|
+
const getRoleBadgeClasses = (role) => {
|
|
149
|
+
switch (role) {
|
|
150
|
+
case 'admin':
|
|
151
|
+
return 'bg-primary text-primary-foreground';
|
|
152
|
+
case 'editor':
|
|
153
|
+
return 'bg-success text-success-foreground';
|
|
154
|
+
case 'viewer':
|
|
155
|
+
return 'bg-muted text-muted-foreground';
|
|
156
|
+
default:
|
|
157
|
+
return 'bg-muted text-muted-foreground';
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const roleBadgeClasses = getRoleBadgeClasses(user.role);
|
|
161
|
+
// Show delete button only if current user is admin
|
|
162
|
+
const showDeleteButton = currentUserRole === 'admin';
|
|
163
|
+
const deleteButtonHtml = showDeleteButton ? '' : 'style="display: none;"';
|
|
164
|
+
return template
|
|
165
|
+
.replace(/{{USER_ID}}/g, user.id)
|
|
166
|
+
.replace(/{{USER_NAME}}/g, user.name || 'No name set')
|
|
167
|
+
.replace(/{{USER_EMAIL}}/g, user.email)
|
|
168
|
+
.replace(/{{USER_ROLE}}/g, user.role)
|
|
169
|
+
.replace(/{{ROLE_BADGE_CLASSES}}/g, roleBadgeClasses)
|
|
170
|
+
.replace('{{ORGANIZATION_ID}}', user.organizationId || 'Default Organization')
|
|
171
|
+
.replace('{{CREATED_AT}}', this.formatDate(user.createdAt))
|
|
172
|
+
.replace('{{UPDATED_AT}}', user.updatedAt ? this.formatDate(user.updatedAt) : 'Never')
|
|
173
|
+
.replace('{{DELETE_BUTTON_STYLE}}', deleteButtonHtml);
|
|
174
|
+
}
|
|
175
|
+
static renderAddUser(allowedRoles = ['admin', 'editor', 'viewer']) {
|
|
176
|
+
const template = this.loadTemplate('admin-add-user.html');
|
|
177
|
+
// Generate role options based on allowed roles
|
|
178
|
+
const roleOptions = allowedRoles
|
|
179
|
+
.map(role => `<option value="${role}">${role.charAt(0).toUpperCase() + role.slice(1)}</option>`)
|
|
180
|
+
.join('');
|
|
181
|
+
return template.replace('{{ROLE_OPTIONS}}', roleOptions);
|
|
182
|
+
}
|
|
183
|
+
static formatDate(dateString) {
|
|
184
|
+
try {
|
|
185
|
+
const date = new Date(dateString);
|
|
186
|
+
return date.toLocaleString('en-US', {
|
|
187
|
+
year: 'numeric',
|
|
188
|
+
month: 'short',
|
|
189
|
+
day: 'numeric',
|
|
190
|
+
hour: '2-digit',
|
|
191
|
+
minute: '2-digit',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
return dateString;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createBetterAuthConfig } from '../auth/auth-config.js';
|
|
2
|
+
import { CryptoService } from './crypto-service.js';
|
|
3
|
+
import { Payload, AuthResult } from '@owox/idp-protocol';
|
|
4
|
+
export declare class TokenService {
|
|
5
|
+
private readonly auth;
|
|
6
|
+
private readonly cryptoService;
|
|
7
|
+
private static readonly DEFAULT_ORGANIZATION_ID;
|
|
8
|
+
constructor(auth: Awaited<ReturnType<typeof createBetterAuthConfig>>, cryptoService: CryptoService);
|
|
9
|
+
introspectToken(token: string): Promise<Payload | null>;
|
|
10
|
+
parseToken(token: string): Promise<Payload | null>;
|
|
11
|
+
verifyToken(token: string): Promise<Payload | null>;
|
|
12
|
+
refreshToken(refreshToken: string): Promise<AuthResult>;
|
|
13
|
+
revokeToken(token: string): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=token-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-service.d.ts","sourceRoot":"","sources":["../../src/services/token-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEzD,qBAAa,YAAY;IAIrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAJhC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAkB;gBAG9C,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,EACxD,aAAa,EAAE,aAAa;IAGzC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IA6BvD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAIlD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IA6BnD,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAuBvD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAwBhD"}
|