@ciscode/authentication-kit 1.1.0 → 1.1.2
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 +57 -73
- package/dist/auth-kit.module.d.ts +7 -0
- package/dist/auth-kit.module.js +50 -0
- package/dist/config/db.config.d.ts +1 -0
- package/dist/config/db.config.js +22 -0
- package/dist/config/passport.config.d.ts +3 -0
- package/dist/config/passport.config.js +272 -0
- package/dist/controllers/admin.controller.d.ts +4 -0
- package/dist/controllers/admin.controller.js +59 -0
- package/dist/controllers/auth.controller.d.ts +23 -0
- package/dist/controllers/auth.controller.js +645 -0
- package/dist/controllers/password-reset.controller.d.ts +8 -0
- package/dist/controllers/password-reset.controller.js +146 -0
- package/dist/controllers/permissions.controller.d.ts +7 -0
- package/dist/controllers/permissions.controller.js +115 -0
- package/dist/controllers/roles.controller.d.ts +7 -0
- package/dist/controllers/roles.controller.js +113 -0
- package/dist/controllers/users.controller.d.ts +8 -0
- package/dist/controllers/users.controller.js +259 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +14 -0
- package/dist/middleware/auth.guard.d.ts +4 -0
- package/dist/middleware/auth.guard.js +39 -0
- package/dist/middleware/authenticate.guard.d.ts +4 -0
- package/dist/middleware/authenticate.guard.js +44 -0
- package/dist/middleware/permission.guard.d.ts +4 -0
- package/dist/middleware/permission.guard.js +52 -0
- package/dist/middleware/tenant.guard.d.ts +4 -0
- package/dist/middleware/tenant.guard.js +39 -0
- package/dist/models/client.model.d.ts +54 -0
- package/dist/models/client.model.js +34 -0
- package/dist/models/permission.model.d.ts +19 -0
- package/dist/models/permission.model.js +17 -0
- package/dist/models/role.model.d.ts +33 -0
- package/dist/models/role.model.js +19 -0
- package/dist/models/tenant.model.d.ts +19 -0
- package/dist/models/tenant.model.js +15 -0
- package/dist/models/user.model.d.ts +63 -0
- package/dist/models/user.model.js +41 -0
- package/dist/standalone.d.ts +1 -0
- package/dist/standalone.js +12 -0
- package/dist/utils/helper.d.ts +1 -0
- package/dist/utils/helper.js +22 -0
- package/package.json +69 -43
- package/.github/workflows/ci .yml +0 -36
- package/.github/workflows/publish.yml +0 -30
- package/CODE_OF_CONDUCT +0 -38
- package/CONTRIBUTING.md +0 -40
- package/SECURITY +0 -31
- package/azure-pipelines.yml +0 -100
- package/src/config/db.config.js +0 -21
- package/src/config/passport.config.js +0 -280
- package/src/controllers/auth.controller.js +0 -566
- package/src/controllers/passwordReset.controller.js +0 -127
- package/src/controllers/permission.controller.js +0 -81
- package/src/controllers/roles.controller.js +0 -108
- package/src/controllers/user.controller.js +0 -283
- package/src/index.js +0 -32
- package/src/middleware/auth.middleware.js +0 -16
- package/src/middleware/authenticate.js +0 -25
- package/src/middleware/rbac.middleware.js +0 -24
- package/src/middleware/tenant.middleware.js +0 -16
- package/src/models/client.model.js +0 -39
- package/src/models/permission.model.js +0 -9
- package/src/models/role.model.js +0 -14
- package/src/models/tenant.model.js +0 -9
- package/src/models/user.model.js +0 -51
- package/src/routes/admin.routes.js +0 -8
- package/src/routes/auth.routes.js +0 -77
- package/src/routes/passwordReset.routes.js +0 -8
- package/src/routes/permission.routes.js +0 -17
- package/src/routes/roles.routes.js +0 -11
- package/src/routes/user.routes.js +0 -22
- package/src/utils/helper.js +0 -26
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
15
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
|
+
};
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.AuthController = void 0;
|
|
19
|
+
const common_1 = require("@nestjs/common");
|
|
20
|
+
const passport_config_1 = __importDefault(require("../config/passport.config"));
|
|
21
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
22
|
+
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
|
23
|
+
const jwks_rsa_1 = __importDefault(require("jwks-rsa"));
|
|
24
|
+
const axios_1 = __importDefault(require("axios"));
|
|
25
|
+
const user_model_1 = __importDefault(require("../models/user.model"));
|
|
26
|
+
const client_model_1 = __importDefault(require("../models/client.model"));
|
|
27
|
+
const role_model_1 = __importDefault(require("../models/role.model"));
|
|
28
|
+
const helper_1 = require("../utils/helper");
|
|
29
|
+
const TENANT_ID = process.env.MICROSOFT_TENANT_ID || 'common';
|
|
30
|
+
const MSAL_MOBILE_CLIENT_ID = process.env.MSAL_MOBILE_CLIENT_ID;
|
|
31
|
+
const resolveJwtExpiry = (value, fallback) => (value || fallback);
|
|
32
|
+
const msJwks = (0, jwks_rsa_1.default)({
|
|
33
|
+
jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys',
|
|
34
|
+
cache: true,
|
|
35
|
+
rateLimit: true,
|
|
36
|
+
jwksRequestsPerMinute: 5,
|
|
37
|
+
});
|
|
38
|
+
function verifyMicrosoftIdToken(idToken) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const getKey = (header, cb) => {
|
|
41
|
+
msJwks
|
|
42
|
+
.getSigningKey(header.kid)
|
|
43
|
+
.then((k) => cb(null, k.getPublicKey()))
|
|
44
|
+
.catch(cb);
|
|
45
|
+
};
|
|
46
|
+
jsonwebtoken_1.default.verify(idToken, getKey, { algorithms: ['RS256'], audience: MSAL_MOBILE_CLIENT_ID }, (err, payload) => (err ? reject(err) : resolve(payload)));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
let AuthController = class AuthController {
|
|
50
|
+
async issueTokensAndRespond(principal, res) {
|
|
51
|
+
const roleDocs = await role_model_1.default.find({ _id: { $in: principal.roles } })
|
|
52
|
+
.select('name permissions -_id')
|
|
53
|
+
.lean();
|
|
54
|
+
const roles = roleDocs.map((r) => r.name);
|
|
55
|
+
const permissions = Array.from(new Set(roleDocs.flatMap((r) => r.permissions)));
|
|
56
|
+
const accessTTL = resolveJwtExpiry(process.env.JWT_ACCESS_TOKEN_EXPIRES_IN, '15m');
|
|
57
|
+
const refreshTTL = resolveJwtExpiry(process.env.JWT_REFRESH_TOKEN_EXPIRES_IN, '7d');
|
|
58
|
+
const payload = {
|
|
59
|
+
id: principal._id,
|
|
60
|
+
email: principal.email,
|
|
61
|
+
tenantId: principal.tenantId,
|
|
62
|
+
roles,
|
|
63
|
+
permissions,
|
|
64
|
+
};
|
|
65
|
+
const accessToken = jsonwebtoken_1.default.sign(payload, process.env.JWT_SECRET, { expiresIn: accessTTL });
|
|
66
|
+
const refreshToken = jsonwebtoken_1.default.sign({ id: principal._id }, process.env.JWT_REFRESH_SECRET, { expiresIn: refreshTTL });
|
|
67
|
+
principal.refreshToken = refreshToken;
|
|
68
|
+
try {
|
|
69
|
+
await principal.save();
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
console.error('Error saving refreshToken:', e);
|
|
73
|
+
}
|
|
74
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
75
|
+
res.cookie('refreshToken', refreshToken, {
|
|
76
|
+
httpOnly: true,
|
|
77
|
+
secure: isProd,
|
|
78
|
+
sameSite: isProd ? 'none' : 'lax',
|
|
79
|
+
path: '/',
|
|
80
|
+
maxAge: (0, helper_1.getMillisecondsFromExpiry)(refreshTTL),
|
|
81
|
+
});
|
|
82
|
+
return res.status(200).json({ accessToken, refreshToken });
|
|
83
|
+
}
|
|
84
|
+
async respondWebOrMobile(req, res, principal) {
|
|
85
|
+
const roleDocs = await role_model_1.default.find({ _id: { $in: principal.roles } })
|
|
86
|
+
.select('name permissions -_id')
|
|
87
|
+
.lean();
|
|
88
|
+
const roles = roleDocs.map((r) => r.name);
|
|
89
|
+
const permissions = Array.from(new Set(roleDocs.flatMap((r) => r.permissions)));
|
|
90
|
+
const accessTTL = resolveJwtExpiry(process.env.JWT_ACCESS_TOKEN_EXPIRES_IN, '15m');
|
|
91
|
+
const refreshTTL = resolveJwtExpiry(process.env.JWT_REFRESH_TOKEN_EXPIRES_IN, '7d');
|
|
92
|
+
const payload = {
|
|
93
|
+
id: principal._id,
|
|
94
|
+
email: principal.email,
|
|
95
|
+
tenantId: principal.tenantId,
|
|
96
|
+
roles,
|
|
97
|
+
permissions,
|
|
98
|
+
};
|
|
99
|
+
const accessToken = jsonwebtoken_1.default.sign(payload, process.env.JWT_SECRET, { expiresIn: accessTTL });
|
|
100
|
+
const refreshToken = jsonwebtoken_1.default.sign({ id: principal._id }, process.env.JWT_REFRESH_SECRET, { expiresIn: refreshTTL });
|
|
101
|
+
principal.refreshToken = refreshToken;
|
|
102
|
+
try {
|
|
103
|
+
await principal.save();
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
console.error('Saving refreshToken failed:', e);
|
|
107
|
+
}
|
|
108
|
+
let mobileRedirect;
|
|
109
|
+
if (req.query.state) {
|
|
110
|
+
try {
|
|
111
|
+
const decoded = JSON.parse(Buffer.from(req.query.state, 'base64url').toString('utf8'));
|
|
112
|
+
mobileRedirect = decoded.redirect;
|
|
113
|
+
}
|
|
114
|
+
catch (_) {
|
|
115
|
+
// ignore
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (mobileRedirect) {
|
|
119
|
+
const url = new URL(mobileRedirect);
|
|
120
|
+
url.searchParams.set('accessToken', accessToken);
|
|
121
|
+
url.searchParams.set('refreshToken', refreshToken);
|
|
122
|
+
return res.redirect(302, url.toString());
|
|
123
|
+
}
|
|
124
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
125
|
+
res.cookie('refreshToken', refreshToken, {
|
|
126
|
+
httpOnly: true,
|
|
127
|
+
secure: isProd,
|
|
128
|
+
sameSite: isProd ? 'none' : 'lax',
|
|
129
|
+
path: '/',
|
|
130
|
+
maxAge: (0, helper_1.getMillisecondsFromExpiry)(refreshTTL),
|
|
131
|
+
});
|
|
132
|
+
return res.status(200).json({ accessToken, refreshToken });
|
|
133
|
+
}
|
|
134
|
+
async registerClient(req, res) {
|
|
135
|
+
try {
|
|
136
|
+
const { email, password, name, roles = [] } = req.body;
|
|
137
|
+
if (!email || !password) {
|
|
138
|
+
return res.status(400).json({ message: 'Email and password are required.' });
|
|
139
|
+
}
|
|
140
|
+
if (await client_model_1.default.findOne({ email })) {
|
|
141
|
+
return res.status(409).json({ message: 'Email already in use.' });
|
|
142
|
+
}
|
|
143
|
+
const salt = await bcryptjs_1.default.genSalt(10);
|
|
144
|
+
const hashed = await bcryptjs_1.default.hash(password, salt);
|
|
145
|
+
const client = new client_model_1.default({ email, password: hashed, name, roles });
|
|
146
|
+
await client.save();
|
|
147
|
+
return res.status(201).json({
|
|
148
|
+
id: client._id,
|
|
149
|
+
email: client.email,
|
|
150
|
+
name: client.name,
|
|
151
|
+
roles: client.roles,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
console.error('registerClient error:', err);
|
|
156
|
+
return res.status(500).json({ message: 'Server error.' });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async clientLogin(req, res) {
|
|
160
|
+
try {
|
|
161
|
+
const { email, password } = req.body;
|
|
162
|
+
if (!email || !password) {
|
|
163
|
+
return res.status(400).json({ message: 'Email and password are required.' });
|
|
164
|
+
}
|
|
165
|
+
const client = await client_model_1.default.findOne({ email })
|
|
166
|
+
.select('+password')
|
|
167
|
+
.populate('roles', 'name permissions');
|
|
168
|
+
if (!client) {
|
|
169
|
+
return res.status(400).json({ message: 'Incorrect email.' });
|
|
170
|
+
}
|
|
171
|
+
const match = await bcryptjs_1.default.compare(password, client.password);
|
|
172
|
+
if (!match) {
|
|
173
|
+
return res.status(400).json({ message: 'Incorrect password.' });
|
|
174
|
+
}
|
|
175
|
+
return this.issueTokensAndRespond(client, res);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
console.error('clientLogin error:', err);
|
|
179
|
+
return res.status(500).json({ message: 'Server error.' });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
localLogin(req, res, next) {
|
|
183
|
+
return passport_config_1.default.authenticate('local', { session: false }, async (err, user, info) => {
|
|
184
|
+
if (err)
|
|
185
|
+
return next(err);
|
|
186
|
+
if (!user)
|
|
187
|
+
return res.status(400).json({ message: (info === null || info === void 0 ? void 0 : info.message) || 'Invalid credentials.' });
|
|
188
|
+
try {
|
|
189
|
+
return this.issueTokensAndRespond(user, res);
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
console.error('localLogin error:', e);
|
|
193
|
+
return res.status(500).json({ message: 'Server error.' });
|
|
194
|
+
}
|
|
195
|
+
})(req, res, next);
|
|
196
|
+
}
|
|
197
|
+
microsoftLogin(req, res, next) {
|
|
198
|
+
const redirect = req.query.redirect;
|
|
199
|
+
const state = redirect ? Buffer.from(JSON.stringify({ redirect }), 'utf8').toString('base64url') : undefined;
|
|
200
|
+
return passport_config_1.default.authenticate('azure_ad_oauth2', {
|
|
201
|
+
session: false,
|
|
202
|
+
state,
|
|
203
|
+
})(req, res, next);
|
|
204
|
+
}
|
|
205
|
+
microsoftCallback(req, res, next) {
|
|
206
|
+
passport_config_1.default.authenticate('azure_ad_oauth2', { session: false }, async (err, user) => {
|
|
207
|
+
if (err)
|
|
208
|
+
return next(err);
|
|
209
|
+
if (!user)
|
|
210
|
+
return res.status(400).json({ message: 'Microsoft authentication failed.' });
|
|
211
|
+
return this.respondWebOrMobile(req, res, user);
|
|
212
|
+
})(req, res, next);
|
|
213
|
+
}
|
|
214
|
+
async microsoftExchange(req, res) {
|
|
215
|
+
try {
|
|
216
|
+
if (!MSAL_MOBILE_CLIENT_ID) {
|
|
217
|
+
console.error('MSAL_MOBILE_CLIENT_ID is not set in environment.');
|
|
218
|
+
return res.status(500).json({ message: 'Server misconfiguration.' });
|
|
219
|
+
}
|
|
220
|
+
const { idToken } = req.body || {};
|
|
221
|
+
if (!idToken) {
|
|
222
|
+
return res.status(400).json({ message: 'idToken is required.' });
|
|
223
|
+
}
|
|
224
|
+
let ms;
|
|
225
|
+
try {
|
|
226
|
+
ms = await verifyMicrosoftIdToken(idToken);
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
console.error('ID token verify failed:', e.message || e);
|
|
230
|
+
return res.status(401).json({ message: 'Invalid Microsoft ID token.' });
|
|
231
|
+
}
|
|
232
|
+
const email = ms.preferred_username || ms.email;
|
|
233
|
+
const name = ms.name;
|
|
234
|
+
const tid = ms.tid;
|
|
235
|
+
if (TENANT_ID && TENANT_ID !== 'common' && tid && tid !== TENANT_ID) {
|
|
236
|
+
return res.status(401).json({ message: 'Tenant mismatch.' });
|
|
237
|
+
}
|
|
238
|
+
if (!email) {
|
|
239
|
+
return res.status(400).json({ message: 'Email claim missing in Microsoft ID token.' });
|
|
240
|
+
}
|
|
241
|
+
const microsoftId = ms.oid || ms.sub;
|
|
242
|
+
let user = await user_model_1.default.findOne({ email });
|
|
243
|
+
if (!user) {
|
|
244
|
+
user = new user_model_1.default({
|
|
245
|
+
email,
|
|
246
|
+
name,
|
|
247
|
+
tenantId: tid || TENANT_ID,
|
|
248
|
+
microsoftId,
|
|
249
|
+
roles: [],
|
|
250
|
+
status: 'active',
|
|
251
|
+
});
|
|
252
|
+
await user.save();
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
let changed = false;
|
|
256
|
+
if (!user.microsoftId) {
|
|
257
|
+
user.microsoftId = microsoftId;
|
|
258
|
+
changed = true;
|
|
259
|
+
}
|
|
260
|
+
if (!user.tenantId) {
|
|
261
|
+
user.tenantId = tid || TENANT_ID;
|
|
262
|
+
changed = true;
|
|
263
|
+
}
|
|
264
|
+
if (changed)
|
|
265
|
+
await user.save();
|
|
266
|
+
}
|
|
267
|
+
return this.issueTokensAndRespond(user, res);
|
|
268
|
+
}
|
|
269
|
+
catch (e) {
|
|
270
|
+
console.error('microsoftExchange error:', e);
|
|
271
|
+
return res.status(500).json({ message: 'Server error.' });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
googleUserLogin(req, res, next) {
|
|
275
|
+
const redirect = req.query.redirect;
|
|
276
|
+
const state = redirect ? Buffer.from(JSON.stringify({ redirect }), 'utf8').toString('base64url') : undefined;
|
|
277
|
+
return passport_config_1.default.authenticate('google-user', { session: false, scope: ['profile', 'email'], state })(req, res, next);
|
|
278
|
+
}
|
|
279
|
+
googleUserCallback(req, res, next) {
|
|
280
|
+
passport_config_1.default.authenticate('google-user', { session: false }, async (err, user) => {
|
|
281
|
+
if (err)
|
|
282
|
+
return next(err);
|
|
283
|
+
if (!user)
|
|
284
|
+
return res.status(400).json({ message: 'Google authentication failed.' });
|
|
285
|
+
return this.respondWebOrMobile(req, res, user);
|
|
286
|
+
})(req, res, next);
|
|
287
|
+
}
|
|
288
|
+
googleClientLogin(req, res, next) {
|
|
289
|
+
const redirect = req.query.redirect;
|
|
290
|
+
const state = redirect ? Buffer.from(JSON.stringify({ redirect }), 'utf8').toString('base64url') : undefined;
|
|
291
|
+
return passport_config_1.default.authenticate('google-client', { session: false, scope: ['profile', 'email'], state })(req, res, next);
|
|
292
|
+
}
|
|
293
|
+
googleClientCallback(req, res, next) {
|
|
294
|
+
passport_config_1.default.authenticate('google-client', { session: false }, async (err, client) => {
|
|
295
|
+
if (err)
|
|
296
|
+
return next(err);
|
|
297
|
+
if (!client)
|
|
298
|
+
return res.status(400).json({ message: 'Google authentication failed.' });
|
|
299
|
+
return this.respondWebOrMobile(req, res, client);
|
|
300
|
+
})(req, res, next);
|
|
301
|
+
}
|
|
302
|
+
async googleExchange(req, res) {
|
|
303
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
304
|
+
try {
|
|
305
|
+
let { code, idToken, type = 'user', tenantId } = req.body || {};
|
|
306
|
+
if (!['user', 'client'].includes(type)) {
|
|
307
|
+
return res.status(400).json({ message: 'invalid type; must be "user" or "client"' });
|
|
308
|
+
}
|
|
309
|
+
let resolvedTenantId;
|
|
310
|
+
if (type === 'user') {
|
|
311
|
+
resolvedTenantId = tenantId || process.env.DEFAULT_TENANT_ID;
|
|
312
|
+
if (!resolvedTenantId) {
|
|
313
|
+
return res.status(400).json({ message: 'tenantId is required for user login.' });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
let email;
|
|
317
|
+
let name;
|
|
318
|
+
let googleId;
|
|
319
|
+
if (code) {
|
|
320
|
+
const tokenResp = await axios_1.default.post('https://oauth2.googleapis.com/token', {
|
|
321
|
+
code,
|
|
322
|
+
client_id: process.env.GOOGLE_CLIENT_ID,
|
|
323
|
+
client_secret: process.env.GOOGLE_CLIENT_SECRET,
|
|
324
|
+
redirect_uri: 'postmessage',
|
|
325
|
+
grant_type: 'authorization_code',
|
|
326
|
+
});
|
|
327
|
+
const { access_token } = tokenResp.data || {};
|
|
328
|
+
if (!access_token) {
|
|
329
|
+
return res.status(401).json({ message: 'Failed to exchange code with Google.' });
|
|
330
|
+
}
|
|
331
|
+
const profileResp = await axios_1.default.get('https://www.googleapis.com/oauth2/v2/userinfo', {
|
|
332
|
+
headers: { Authorization: `Bearer ${access_token}` },
|
|
333
|
+
});
|
|
334
|
+
email = (_a = profileResp.data) === null || _a === void 0 ? void 0 : _a.email;
|
|
335
|
+
name = ((_b = profileResp.data) === null || _b === void 0 ? void 0 : _b.name) || ((_c = profileResp.data) === null || _c === void 0 ? void 0 : _c.given_name) || '';
|
|
336
|
+
googleId = (_d = profileResp.data) === null || _d === void 0 ? void 0 : _d.id;
|
|
337
|
+
}
|
|
338
|
+
else if (idToken) {
|
|
339
|
+
const verifyResp = await axios_1.default.get('https://oauth2.googleapis.com/tokeninfo', {
|
|
340
|
+
params: { id_token: idToken },
|
|
341
|
+
});
|
|
342
|
+
email = (_e = verifyResp.data) === null || _e === void 0 ? void 0 : _e.email;
|
|
343
|
+
name = ((_f = verifyResp.data) === null || _f === void 0 ? void 0 : _f.name) || '';
|
|
344
|
+
googleId = (_g = verifyResp.data) === null || _g === void 0 ? void 0 : _g.sub;
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
return res.status(400).json({ message: 'code or idToken is required' });
|
|
348
|
+
}
|
|
349
|
+
if (!email)
|
|
350
|
+
return res.status(400).json({ message: 'Google profile missing email.' });
|
|
351
|
+
const Model = type === 'user' ? user_model_1.default : client_model_1.default;
|
|
352
|
+
let principal = await Model.findOne({ $or: [{ email }, { googleId }] });
|
|
353
|
+
if (!principal) {
|
|
354
|
+
principal = new Model(type === 'user'
|
|
355
|
+
? { email, name, googleId, tenantId: resolvedTenantId, roles: [], status: 'active' }
|
|
356
|
+
: { email, name, googleId, roles: [] });
|
|
357
|
+
await principal.save();
|
|
358
|
+
}
|
|
359
|
+
else if (!principal.googleId) {
|
|
360
|
+
principal.googleId = googleId;
|
|
361
|
+
if (type === 'user' && !principal.tenantId)
|
|
362
|
+
principal.tenantId = resolvedTenantId;
|
|
363
|
+
await principal.save();
|
|
364
|
+
}
|
|
365
|
+
const roleDocs = await role_model_1.default.find({ _id: { $in: principal.roles } })
|
|
366
|
+
.select('name permissions -_id').lean();
|
|
367
|
+
const roles = roleDocs.map((r) => r.name);
|
|
368
|
+
const permissions = Array.from(new Set(roleDocs.flatMap((r) => r.permissions)));
|
|
369
|
+
const accessTTL = resolveJwtExpiry(process.env.JWT_ACCESS_TOKEN_EXPIRES_IN, '15m');
|
|
370
|
+
const refreshTTL = resolveJwtExpiry(process.env.JWT_REFRESH_TOKEN_EXPIRES_IN, '7d');
|
|
371
|
+
const payload = { id: principal._id, email: principal.email, tenantId: principal.tenantId, roles, permissions };
|
|
372
|
+
const accessToken = jsonwebtoken_1.default.sign(payload, process.env.JWT_SECRET, { expiresIn: accessTTL });
|
|
373
|
+
const refreshToken = jsonwebtoken_1.default.sign({ id: principal._id }, process.env.JWT_REFRESH_SECRET, { expiresIn: refreshTTL });
|
|
374
|
+
principal.refreshToken = refreshToken;
|
|
375
|
+
try {
|
|
376
|
+
await principal.save();
|
|
377
|
+
}
|
|
378
|
+
catch (e) {
|
|
379
|
+
console.error('Saving refreshToken failed:', e);
|
|
380
|
+
}
|
|
381
|
+
const isProd = process.env.NODE_ENV === 'production';
|
|
382
|
+
res.cookie('refreshToken', refreshToken, {
|
|
383
|
+
httpOnly: true,
|
|
384
|
+
secure: isProd,
|
|
385
|
+
sameSite: isProd ? 'none' : 'lax',
|
|
386
|
+
path: '/',
|
|
387
|
+
maxAge: (0, helper_1.getMillisecondsFromExpiry)(refreshTTL),
|
|
388
|
+
});
|
|
389
|
+
return res.status(200).json({ accessToken, refreshToken });
|
|
390
|
+
}
|
|
391
|
+
catch (err) {
|
|
392
|
+
console.error('googleExchange error:', ((_h = err === null || err === void 0 ? void 0 : err.response) === null || _h === void 0 ? void 0 : _h.data) || err.message || err);
|
|
393
|
+
return res.status(500).json({ message: 'Server error during Google exchange.' });
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
facebookUserLogin(req, res, next) {
|
|
397
|
+
const redirect = req.query.redirect;
|
|
398
|
+
const state = redirect ? Buffer.from(JSON.stringify({ redirect }), 'utf8').toString('base64url') : undefined;
|
|
399
|
+
return passport_config_1.default.authenticate('facebook-user', { session: false, scope: ['email'], state })(req, res, next);
|
|
400
|
+
}
|
|
401
|
+
facebookUserCallback(req, res, next) {
|
|
402
|
+
passport_config_1.default.authenticate('facebook-user', { session: false }, async (err, user) => {
|
|
403
|
+
if (err)
|
|
404
|
+
return next(err);
|
|
405
|
+
if (!user)
|
|
406
|
+
return res.status(400).json({ message: 'Facebook authentication failed.' });
|
|
407
|
+
return this.respondWebOrMobile(req, res, user);
|
|
408
|
+
})(req, res, next);
|
|
409
|
+
}
|
|
410
|
+
facebookClientLogin(req, res, next) {
|
|
411
|
+
const redirect = req.query.redirect;
|
|
412
|
+
const state = redirect ? Buffer.from(JSON.stringify({ redirect }), 'utf8').toString('base64url') : undefined;
|
|
413
|
+
return passport_config_1.default.authenticate('facebook-client', { session: false, scope: ['email'], state })(req, res, next);
|
|
414
|
+
}
|
|
415
|
+
facebookClientCallback(req, res, next) {
|
|
416
|
+
passport_config_1.default.authenticate('facebook-client', { session: false }, async (err, client) => {
|
|
417
|
+
if (err)
|
|
418
|
+
return next(err);
|
|
419
|
+
if (!client)
|
|
420
|
+
return res.status(400).json({ message: 'Facebook authentication failed.' });
|
|
421
|
+
return this.respondWebOrMobile(req, res, client);
|
|
422
|
+
})(req, res, next);
|
|
423
|
+
}
|
|
424
|
+
microsoftClientLogin(req, res, next) {
|
|
425
|
+
const redirect = req.query.redirect;
|
|
426
|
+
const state = redirect ? Buffer.from(JSON.stringify({ redirect }), 'utf8').toString('base64url') : undefined;
|
|
427
|
+
return passport_config_1.default.authenticate('azure_ad_oauth2_client', { session: false, state })(req, res, next);
|
|
428
|
+
}
|
|
429
|
+
microsoftClientCallback(req, res, next) {
|
|
430
|
+
passport_config_1.default.authenticate('azure_ad_oauth2_client', { session: false }, async (err, client) => {
|
|
431
|
+
if (err)
|
|
432
|
+
return next(err);
|
|
433
|
+
if (!client)
|
|
434
|
+
return res.status(400).json({ message: 'Microsoft authentication failed.' });
|
|
435
|
+
return this.respondWebOrMobile(req, res, client);
|
|
436
|
+
})(req, res, next);
|
|
437
|
+
}
|
|
438
|
+
async refreshToken(req, res) {
|
|
439
|
+
var _a;
|
|
440
|
+
try {
|
|
441
|
+
const refreshToken = ((_a = req.cookies) === null || _a === void 0 ? void 0 : _a.refreshToken) || req.body.refreshToken;
|
|
442
|
+
if (!refreshToken) {
|
|
443
|
+
return res.status(401).json({ message: 'Refresh token missing.' });
|
|
444
|
+
}
|
|
445
|
+
let decoded;
|
|
446
|
+
try {
|
|
447
|
+
decoded = jsonwebtoken_1.default.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
const msg = err.name === 'TokenExpiredError' ? 'Refresh token expired.' : 'Invalid refresh token.';
|
|
451
|
+
return res.status(401).json({ message: msg });
|
|
452
|
+
}
|
|
453
|
+
let principal = await user_model_1.default.findById(decoded.id);
|
|
454
|
+
let principalType = 'user';
|
|
455
|
+
if (!principal) {
|
|
456
|
+
principal = await client_model_1.default.findById(decoded.id);
|
|
457
|
+
principalType = 'client';
|
|
458
|
+
}
|
|
459
|
+
if (!principal)
|
|
460
|
+
return res.status(401).json({ message: 'Account not found.' });
|
|
461
|
+
if (principal.refreshToken !== refreshToken) {
|
|
462
|
+
return res.status(401).json({ message: 'Refresh token mismatch.' });
|
|
463
|
+
}
|
|
464
|
+
const roleDocs = await role_model_1.default.find({ _id: { $in: principal.roles } })
|
|
465
|
+
.select('name permissions -_id').lean();
|
|
466
|
+
const roles = roleDocs.map((r) => r.name);
|
|
467
|
+
const permissions = Array.from(new Set(roleDocs.flatMap((r) => r.permissions)));
|
|
468
|
+
const payload = {
|
|
469
|
+
id: principal._id,
|
|
470
|
+
email: principal.email,
|
|
471
|
+
tenantId: principal.tenantId,
|
|
472
|
+
roles,
|
|
473
|
+
permissions,
|
|
474
|
+
};
|
|
475
|
+
const accessTokenExpiresIn = resolveJwtExpiry(process.env.JWT_ACCESS_TOKEN_EXPIRES_IN, '15m');
|
|
476
|
+
const accessToken = jsonwebtoken_1.default.sign(payload, process.env.JWT_SECRET, { expiresIn: accessTokenExpiresIn });
|
|
477
|
+
return res.status(200).json({ accessToken, type: principalType });
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
console.error('[Refresh] Unexpected error:', error);
|
|
481
|
+
return res.status(500).json({ message: 'Server error during token refresh.' });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
exports.AuthController = AuthController;
|
|
486
|
+
__decorate([
|
|
487
|
+
(0, common_1.Post)('clients/register'),
|
|
488
|
+
__param(0, (0, common_1.Req)()),
|
|
489
|
+
__param(1, (0, common_1.Res)()),
|
|
490
|
+
__metadata("design:type", Function),
|
|
491
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
492
|
+
__metadata("design:returntype", Promise)
|
|
493
|
+
], AuthController.prototype, "registerClient", null);
|
|
494
|
+
__decorate([
|
|
495
|
+
(0, common_1.Post)('clients/login'),
|
|
496
|
+
__param(0, (0, common_1.Req)()),
|
|
497
|
+
__param(1, (0, common_1.Res)()),
|
|
498
|
+
__metadata("design:type", Function),
|
|
499
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
500
|
+
__metadata("design:returntype", Promise)
|
|
501
|
+
], AuthController.prototype, "clientLogin", null);
|
|
502
|
+
__decorate([
|
|
503
|
+
(0, common_1.Post)('login'),
|
|
504
|
+
__param(0, (0, common_1.Req)()),
|
|
505
|
+
__param(1, (0, common_1.Res)()),
|
|
506
|
+
__param(2, (0, common_1.Next)()),
|
|
507
|
+
__metadata("design:type", Function),
|
|
508
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
509
|
+
__metadata("design:returntype", void 0)
|
|
510
|
+
], AuthController.prototype, "localLogin", null);
|
|
511
|
+
__decorate([
|
|
512
|
+
(0, common_1.Get)('microsoft'),
|
|
513
|
+
__param(0, (0, common_1.Req)()),
|
|
514
|
+
__param(1, (0, common_1.Res)()),
|
|
515
|
+
__param(2, (0, common_1.Next)()),
|
|
516
|
+
__metadata("design:type", Function),
|
|
517
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
518
|
+
__metadata("design:returntype", void 0)
|
|
519
|
+
], AuthController.prototype, "microsoftLogin", null);
|
|
520
|
+
__decorate([
|
|
521
|
+
(0, common_1.Get)('microsoft/callback'),
|
|
522
|
+
__param(0, (0, common_1.Req)()),
|
|
523
|
+
__param(1, (0, common_1.Res)()),
|
|
524
|
+
__param(2, (0, common_1.Next)()),
|
|
525
|
+
__metadata("design:type", Function),
|
|
526
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
527
|
+
__metadata("design:returntype", void 0)
|
|
528
|
+
], AuthController.prototype, "microsoftCallback", null);
|
|
529
|
+
__decorate([
|
|
530
|
+
(0, common_1.Post)('microsoft/exchange'),
|
|
531
|
+
__param(0, (0, common_1.Req)()),
|
|
532
|
+
__param(1, (0, common_1.Res)()),
|
|
533
|
+
__metadata("design:type", Function),
|
|
534
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
535
|
+
__metadata("design:returntype", Promise)
|
|
536
|
+
], AuthController.prototype, "microsoftExchange", null);
|
|
537
|
+
__decorate([
|
|
538
|
+
(0, common_1.Get)('google'),
|
|
539
|
+
__param(0, (0, common_1.Req)()),
|
|
540
|
+
__param(1, (0, common_1.Res)()),
|
|
541
|
+
__param(2, (0, common_1.Next)()),
|
|
542
|
+
__metadata("design:type", Function),
|
|
543
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
544
|
+
__metadata("design:returntype", void 0)
|
|
545
|
+
], AuthController.prototype, "googleUserLogin", null);
|
|
546
|
+
__decorate([
|
|
547
|
+
(0, common_1.Get)('google/callback'),
|
|
548
|
+
__param(0, (0, common_1.Req)()),
|
|
549
|
+
__param(1, (0, common_1.Res)()),
|
|
550
|
+
__param(2, (0, common_1.Next)()),
|
|
551
|
+
__metadata("design:type", Function),
|
|
552
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
553
|
+
__metadata("design:returntype", void 0)
|
|
554
|
+
], AuthController.prototype, "googleUserCallback", null);
|
|
555
|
+
__decorate([
|
|
556
|
+
(0, common_1.Get)('client/google'),
|
|
557
|
+
__param(0, (0, common_1.Req)()),
|
|
558
|
+
__param(1, (0, common_1.Res)()),
|
|
559
|
+
__param(2, (0, common_1.Next)()),
|
|
560
|
+
__metadata("design:type", Function),
|
|
561
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
562
|
+
__metadata("design:returntype", void 0)
|
|
563
|
+
], AuthController.prototype, "googleClientLogin", null);
|
|
564
|
+
__decorate([
|
|
565
|
+
(0, common_1.Get)('client/google/callback'),
|
|
566
|
+
__param(0, (0, common_1.Req)()),
|
|
567
|
+
__param(1, (0, common_1.Res)()),
|
|
568
|
+
__param(2, (0, common_1.Next)()),
|
|
569
|
+
__metadata("design:type", Function),
|
|
570
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
571
|
+
__metadata("design:returntype", void 0)
|
|
572
|
+
], AuthController.prototype, "googleClientCallback", null);
|
|
573
|
+
__decorate([
|
|
574
|
+
(0, common_1.Post)('google/exchange'),
|
|
575
|
+
__param(0, (0, common_1.Req)()),
|
|
576
|
+
__param(1, (0, common_1.Res)()),
|
|
577
|
+
__metadata("design:type", Function),
|
|
578
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
579
|
+
__metadata("design:returntype", Promise)
|
|
580
|
+
], AuthController.prototype, "googleExchange", null);
|
|
581
|
+
__decorate([
|
|
582
|
+
(0, common_1.Get)('facebook'),
|
|
583
|
+
__param(0, (0, common_1.Req)()),
|
|
584
|
+
__param(1, (0, common_1.Res)()),
|
|
585
|
+
__param(2, (0, common_1.Next)()),
|
|
586
|
+
__metadata("design:type", Function),
|
|
587
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
588
|
+
__metadata("design:returntype", void 0)
|
|
589
|
+
], AuthController.prototype, "facebookUserLogin", null);
|
|
590
|
+
__decorate([
|
|
591
|
+
(0, common_1.Get)('facebook/callback'),
|
|
592
|
+
__param(0, (0, common_1.Req)()),
|
|
593
|
+
__param(1, (0, common_1.Res)()),
|
|
594
|
+
__param(2, (0, common_1.Next)()),
|
|
595
|
+
__metadata("design:type", Function),
|
|
596
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
597
|
+
__metadata("design:returntype", void 0)
|
|
598
|
+
], AuthController.prototype, "facebookUserCallback", null);
|
|
599
|
+
__decorate([
|
|
600
|
+
(0, common_1.Get)('client/facebook'),
|
|
601
|
+
__param(0, (0, common_1.Req)()),
|
|
602
|
+
__param(1, (0, common_1.Res)()),
|
|
603
|
+
__param(2, (0, common_1.Next)()),
|
|
604
|
+
__metadata("design:type", Function),
|
|
605
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
606
|
+
__metadata("design:returntype", void 0)
|
|
607
|
+
], AuthController.prototype, "facebookClientLogin", null);
|
|
608
|
+
__decorate([
|
|
609
|
+
(0, common_1.Get)('client/facebook/callback'),
|
|
610
|
+
__param(0, (0, common_1.Req)()),
|
|
611
|
+
__param(1, (0, common_1.Res)()),
|
|
612
|
+
__param(2, (0, common_1.Next)()),
|
|
613
|
+
__metadata("design:type", Function),
|
|
614
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
615
|
+
__metadata("design:returntype", void 0)
|
|
616
|
+
], AuthController.prototype, "facebookClientCallback", null);
|
|
617
|
+
__decorate([
|
|
618
|
+
(0, common_1.Get)('client/microsoft'),
|
|
619
|
+
__param(0, (0, common_1.Req)()),
|
|
620
|
+
__param(1, (0, common_1.Res)()),
|
|
621
|
+
__param(2, (0, common_1.Next)()),
|
|
622
|
+
__metadata("design:type", Function),
|
|
623
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
624
|
+
__metadata("design:returntype", void 0)
|
|
625
|
+
], AuthController.prototype, "microsoftClientLogin", null);
|
|
626
|
+
__decorate([
|
|
627
|
+
(0, common_1.Get)('client/microsoft/callback'),
|
|
628
|
+
__param(0, (0, common_1.Req)()),
|
|
629
|
+
__param(1, (0, common_1.Res)()),
|
|
630
|
+
__param(2, (0, common_1.Next)()),
|
|
631
|
+
__metadata("design:type", Function),
|
|
632
|
+
__metadata("design:paramtypes", [Object, Object, Function]),
|
|
633
|
+
__metadata("design:returntype", void 0)
|
|
634
|
+
], AuthController.prototype, "microsoftClientCallback", null);
|
|
635
|
+
__decorate([
|
|
636
|
+
(0, common_1.Post)('refresh-token'),
|
|
637
|
+
__param(0, (0, common_1.Req)()),
|
|
638
|
+
__param(1, (0, common_1.Res)()),
|
|
639
|
+
__metadata("design:type", Function),
|
|
640
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
641
|
+
__metadata("design:returntype", Promise)
|
|
642
|
+
], AuthController.prototype, "refreshToken", null);
|
|
643
|
+
exports.AuthController = AuthController = __decorate([
|
|
644
|
+
(0, common_1.Controller)('api/auth')
|
|
645
|
+
], AuthController);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Request, Response } from 'express';
|
|
2
|
+
export declare class PasswordResetController {
|
|
3
|
+
private requestPasswordReset;
|
|
4
|
+
private resetPassword;
|
|
5
|
+
forgotPassword(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
|
|
6
|
+
requestPasswordResetRoute(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
|
|
7
|
+
resetPasswordRoute(req: Request, res: Response): Promise<Response<any, Record<string, any>>>;
|
|
8
|
+
}
|