aaspai-authx 0.1.0 → 0.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/dist/express/index.cjs +239 -56
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.js +239 -56
- package/dist/express/index.js.map +1 -1
- package/dist/index.cjs +265 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -69
- package/dist/index.d.ts +70 -69
- package/dist/index.js +265 -82
- package/dist/index.js.map +1 -1
- package/dist/nest/index.cjs +239 -56
- package/dist/nest/index.cjs.map +1 -1
- package/dist/nest/index.js +239 -56
- package/dist/nest/index.js.map +1 -1
- package/package.json +1 -1
package/dist/express/index.js
CHANGED
|
@@ -6,24 +6,6 @@ import express, {
|
|
|
6
6
|
} from "express";
|
|
7
7
|
import jwt4 from "jsonwebtoken";
|
|
8
8
|
|
|
9
|
-
// src/core/utils.ts
|
|
10
|
-
function baseProjectCookieOptionsFrom(cookie) {
|
|
11
|
-
const base = {
|
|
12
|
-
secure: cookie.secure ?? false,
|
|
13
|
-
sameSite: cookie.sameSite ?? "lax",
|
|
14
|
-
path: cookie.path ?? "/",
|
|
15
|
-
maxAge: cookie.maxAgeMs
|
|
16
|
-
};
|
|
17
|
-
if (cookie.domain) base.domain = cookie.domain;
|
|
18
|
-
return base;
|
|
19
|
-
}
|
|
20
|
-
function hasAnyRole(session, roles) {
|
|
21
|
-
if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
return roles.some((role) => session.roles.includes(role));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
9
|
// src/config/loadConfig.ts
|
|
28
10
|
function loadConfig() {
|
|
29
11
|
return {
|
|
@@ -87,6 +69,37 @@ function isPlainObject(value) {
|
|
|
87
69
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
88
70
|
}
|
|
89
71
|
|
|
72
|
+
// src/core/utils.ts
|
|
73
|
+
function baseProjectCookieOptionsFrom(cookie) {
|
|
74
|
+
const base = {
|
|
75
|
+
secure: cookie.secure ?? false,
|
|
76
|
+
sameSite: cookie.sameSite ?? "lax",
|
|
77
|
+
path: cookie.path ?? "/",
|
|
78
|
+
maxAge: cookie.maxAgeMs
|
|
79
|
+
};
|
|
80
|
+
if (cookie.domain) base.domain = cookie.domain;
|
|
81
|
+
return base;
|
|
82
|
+
}
|
|
83
|
+
function buildClearCookieOptions(cookie) {
|
|
84
|
+
const opts = {
|
|
85
|
+
httpOnly: true,
|
|
86
|
+
// not strictly required but fine
|
|
87
|
+
secure: cookie.secure ?? false,
|
|
88
|
+
sameSite: cookie.sameSite ?? "lax",
|
|
89
|
+
path: cookie.path ?? "/"
|
|
90
|
+
};
|
|
91
|
+
if (cookie.domain) {
|
|
92
|
+
opts.domain = cookie.domain;
|
|
93
|
+
}
|
|
94
|
+
return opts;
|
|
95
|
+
}
|
|
96
|
+
function hasAnyRole(session, roles) {
|
|
97
|
+
if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
return roles.some((role) => session.roles.includes(role));
|
|
101
|
+
}
|
|
102
|
+
|
|
90
103
|
// src/core/roles.config.ts
|
|
91
104
|
var PLATFORM_ROLES = [
|
|
92
105
|
{
|
|
@@ -131,10 +144,14 @@ function buildSession(payload) {
|
|
|
131
144
|
roles: normalizedRoles,
|
|
132
145
|
permissions
|
|
133
146
|
};
|
|
147
|
+
if (payload?.firstName) session.firstName = payload.firstName;
|
|
148
|
+
if (payload?.lastName) session.lastName = payload.lastName;
|
|
134
149
|
if (payload?.projectId) session.projectId = payload.projectId;
|
|
135
150
|
if (payload?.orgId) session.orgId = payload.orgId;
|
|
136
151
|
if (payload?.org_id) session.org_id = payload.org_id;
|
|
137
152
|
if (payload?.authType) session.authType = payload.authType;
|
|
153
|
+
if (payload?.createdAt) session.createdAt = payload.createdAt;
|
|
154
|
+
if (payload?.metadata) session.metadata = payload.metadata;
|
|
138
155
|
Object.keys(payload || {}).forEach((key) => {
|
|
139
156
|
if (![
|
|
140
157
|
"sub",
|
|
@@ -188,7 +205,7 @@ var MetadataSchema = new mongoose2.Schema(
|
|
|
188
205
|
);
|
|
189
206
|
var OrgUserSchema = new mongoose2.Schema(
|
|
190
207
|
{
|
|
191
|
-
id: { type: String, default: uuid(), index: true },
|
|
208
|
+
id: { type: String, default: uuid(), index: true, unique: true },
|
|
192
209
|
email: { type: String, required: true, unique: true },
|
|
193
210
|
firstName: { type: String, required: true },
|
|
194
211
|
lastName: { type: String, required: true },
|
|
@@ -308,10 +325,14 @@ function requireAuth() {
|
|
|
308
325
|
const session = buildSession({
|
|
309
326
|
sub: user.id.toString(),
|
|
310
327
|
email: user.email,
|
|
328
|
+
firstName: user.firstName,
|
|
329
|
+
lastName: user.lastName,
|
|
330
|
+
metadata: user.metadata || [],
|
|
311
331
|
roles: user.roles || [],
|
|
312
332
|
orgId: user.orgId,
|
|
313
333
|
org_id: user.orgId,
|
|
314
|
-
projectId: user.projectId
|
|
334
|
+
projectId: user.projectId,
|
|
335
|
+
createdAt: user.createdAt
|
|
315
336
|
});
|
|
316
337
|
session.authType = "api-key";
|
|
317
338
|
session.projectId = readProjectId(req) || user.projectId || void 0;
|
|
@@ -477,14 +498,14 @@ var AuthAdminService = class {
|
|
|
477
498
|
async createUserInRealm(payload) {
|
|
478
499
|
const hashedPassword = payload.credentials?.[0]?.value ? await bcrypt.hash(payload.credentials[0].value, 10) : void 0;
|
|
479
500
|
const user = await OrgUser.create({
|
|
480
|
-
|
|
501
|
+
id: crypto.randomUUID(),
|
|
481
502
|
email: payload.email,
|
|
482
503
|
firstName: payload.firstName,
|
|
483
504
|
lastName: payload.lastName,
|
|
484
505
|
projectId: payload.projectId,
|
|
485
506
|
emailVerified: payload.emailVerified || false,
|
|
486
507
|
passwordHash: hashedPassword,
|
|
487
|
-
|
|
508
|
+
metadata: payload.metadata || []
|
|
488
509
|
});
|
|
489
510
|
return user;
|
|
490
511
|
}
|
|
@@ -572,29 +593,13 @@ var EmailService = class {
|
|
|
572
593
|
}
|
|
573
594
|
};
|
|
574
595
|
|
|
575
|
-
// src/utils/cookie.ts
|
|
576
|
-
function cookieOpts(isRefresh = false) {
|
|
577
|
-
const maxAge = isRefresh ? config.cookies.refreshTtlMs : config.cookies.accessTtlMs;
|
|
578
|
-
const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
|
|
579
|
-
return {
|
|
580
|
-
httpOnly: true,
|
|
581
|
-
secure,
|
|
582
|
-
sameSite: "none",
|
|
583
|
-
domain: process.env.COOKIE_DOMAIN,
|
|
584
|
-
maxAge
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
function clearOpts() {
|
|
588
|
-
const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
|
|
589
|
-
return {
|
|
590
|
-
domain: process.env.COOKIE_DOMAIN,
|
|
591
|
-
sameSite: "none",
|
|
592
|
-
secure
|
|
593
|
-
};
|
|
594
|
-
}
|
|
595
|
-
|
|
596
596
|
// src/express/auth.routes.ts
|
|
597
597
|
function createAuthRouter(options = {}) {
|
|
598
|
+
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
|
599
|
+
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
600
|
+
const googleRedirectUri = process.env.GOOGLE_REDIRECT_URI;
|
|
601
|
+
const googleDefaultRedirect = process.env.GOOGLE_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
|
|
602
|
+
const isGoogleEnabled = !!googleClientId && !!googleClientSecret && !!googleRedirectUri;
|
|
598
603
|
if (options.config) {
|
|
599
604
|
configureAuthX(options.config);
|
|
600
605
|
}
|
|
@@ -619,8 +624,10 @@ function createAuthRouter(options = {}) {
|
|
|
619
624
|
);
|
|
620
625
|
r.post("/login", validateLogin, async (req, res) => {
|
|
621
626
|
const { email: emailAddress, password } = req.body || {};
|
|
627
|
+
console.log(emailAddress, password, "body");
|
|
622
628
|
try {
|
|
623
629
|
const user = await OrgUser.findOne({ email: emailAddress }).select("+password").lean();
|
|
630
|
+
console.log(user, "user");
|
|
624
631
|
if (!user) {
|
|
625
632
|
return res.status(400).json({
|
|
626
633
|
error: "Invalid email or password",
|
|
@@ -673,13 +680,13 @@ function createAuthRouter(options = {}) {
|
|
|
673
680
|
firstName,
|
|
674
681
|
lastName,
|
|
675
682
|
projectId,
|
|
676
|
-
credentials: [{ type: "password", value: password, temporary: false }]
|
|
683
|
+
credentials: [{ type: "password", value: password, temporary: false }],
|
|
684
|
+
metadata
|
|
677
685
|
});
|
|
678
686
|
await authAdmin.assignRealmRole(kcUser.id, "platform_user");
|
|
679
687
|
const user = await OrgUser.findOneAndUpdate(
|
|
680
688
|
{ email: kcUser.email },
|
|
681
689
|
{
|
|
682
|
-
id: kcUser.id,
|
|
683
690
|
email: kcUser.email,
|
|
684
691
|
firstName,
|
|
685
692
|
lastName,
|
|
@@ -719,8 +726,9 @@ function createAuthRouter(options = {}) {
|
|
|
719
726
|
return res.json(req.user || null);
|
|
720
727
|
});
|
|
721
728
|
r.post("/logout", async (_req, res) => {
|
|
722
|
-
|
|
723
|
-
res.clearCookie("
|
|
729
|
+
const clearOptions = buildClearCookieOptions(cookieConfig);
|
|
730
|
+
res.clearCookie("access_token", clearOptions);
|
|
731
|
+
res.clearCookie("refresh_token", clearOptions);
|
|
724
732
|
res.json({ ok: true });
|
|
725
733
|
});
|
|
726
734
|
r.put("/:userId/metadata", requireAuth(), async (req, res) => {
|
|
@@ -959,16 +967,186 @@ function createAuthRouter(options = {}) {
|
|
|
959
967
|
const user = await OrgUser.findOne({ email: req.query.email }).lean();
|
|
960
968
|
res.json(user || null);
|
|
961
969
|
});
|
|
962
|
-
r.get("/google",
|
|
963
|
-
|
|
970
|
+
r.get("/google", (req, res) => {
|
|
971
|
+
if (!isGoogleEnabled) {
|
|
972
|
+
return res.status(500).json({ error: "Google login not configured" });
|
|
973
|
+
}
|
|
974
|
+
const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
|
|
975
|
+
const params = new URLSearchParams({
|
|
976
|
+
client_id: googleClientId,
|
|
977
|
+
redirect_uri: googleRedirectUri,
|
|
978
|
+
response_type: "code",
|
|
979
|
+
scope: "openid email profile",
|
|
980
|
+
access_type: "offline",
|
|
981
|
+
prompt: "consent",
|
|
982
|
+
state
|
|
983
|
+
});
|
|
984
|
+
const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
985
|
+
res.redirect(url);
|
|
964
986
|
});
|
|
965
|
-
r.get("/google/callback", async (
|
|
966
|
-
|
|
967
|
-
"
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
);
|
|
971
|
-
|
|
987
|
+
r.get("/google/callback", async (req, res) => {
|
|
988
|
+
if (!isGoogleEnabled) {
|
|
989
|
+
return res.status(500).json({ error: "Google login not configured" });
|
|
990
|
+
}
|
|
991
|
+
const code = String(req.query.code || "");
|
|
992
|
+
const state = req.query.state ? String(req.query.state) : "";
|
|
993
|
+
if (!code) {
|
|
994
|
+
return res.status(400).json({ ok: false, error: "Missing authorization code" });
|
|
995
|
+
}
|
|
996
|
+
try {
|
|
997
|
+
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
998
|
+
method: "POST",
|
|
999
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1000
|
+
body: new URLSearchParams({
|
|
1001
|
+
code,
|
|
1002
|
+
client_id: googleClientId,
|
|
1003
|
+
client_secret: googleClientSecret,
|
|
1004
|
+
redirect_uri: googleRedirectUri,
|
|
1005
|
+
grant_type: "authorization_code"
|
|
1006
|
+
})
|
|
1007
|
+
});
|
|
1008
|
+
if (!tokenRes.ok) {
|
|
1009
|
+
const errJson = await tokenRes.json().catch(() => ({}));
|
|
1010
|
+
console.error("Google token error", errJson);
|
|
1011
|
+
return res.status(400).json({ ok: false, error: "Failed to exchange Google code" });
|
|
1012
|
+
}
|
|
1013
|
+
const tokenJson = await tokenRes.json();
|
|
1014
|
+
if (!tokenJson.id_token) {
|
|
1015
|
+
return res.status(400).json({ ok: false, error: "Missing id_token from Google" });
|
|
1016
|
+
}
|
|
1017
|
+
const decoded = jwt4.decode(tokenJson.id_token);
|
|
1018
|
+
const email2 = decoded?.email;
|
|
1019
|
+
if (!email2) {
|
|
1020
|
+
return res.status(400).json({ ok: false, error: "Google account has no email" });
|
|
1021
|
+
}
|
|
1022
|
+
const emailVerified = decoded.email_verified ?? true;
|
|
1023
|
+
const firstName = decoded.given_name || "";
|
|
1024
|
+
const lastName = decoded.family_name || "";
|
|
1025
|
+
let user = await OrgUser.findOne({ email: email2 }).lean();
|
|
1026
|
+
if (!user) {
|
|
1027
|
+
const created = await OrgUser.create({
|
|
1028
|
+
email: email2,
|
|
1029
|
+
firstName,
|
|
1030
|
+
lastName,
|
|
1031
|
+
emailVerified,
|
|
1032
|
+
roles: ["platform_user"],
|
|
1033
|
+
projectId: null,
|
|
1034
|
+
metadata: []
|
|
1035
|
+
// you can also store googleId: decoded.sub
|
|
1036
|
+
});
|
|
1037
|
+
user = created.toObject();
|
|
1038
|
+
}
|
|
1039
|
+
const tokens = generateTokens(user);
|
|
1040
|
+
setAuthCookies(res, tokens, cookieConfig);
|
|
1041
|
+
const redirectTo = state ? decodeURIComponent(state) : googleDefaultRedirect;
|
|
1042
|
+
res.redirect(redirectTo);
|
|
1043
|
+
} catch (err) {
|
|
1044
|
+
console.error("Google callback error", err);
|
|
1045
|
+
const redirectError = googleDefaultRedirect.includes("?") ? `${googleDefaultRedirect}&error=google_login_failed` : `${googleDefaultRedirect}?error=google_login_failed`;
|
|
1046
|
+
res.redirect(redirectError);
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
1049
|
+
r.get("/github", (req, res) => {
|
|
1050
|
+
const githubClientId = process.env.GITHUB_CLIENT_ID;
|
|
1051
|
+
const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
|
|
1052
|
+
if (!githubClientId || !githubRedirectUri) {
|
|
1053
|
+
return res.status(500).json({ error: "GitHub login not configured" });
|
|
1054
|
+
}
|
|
1055
|
+
const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
|
|
1056
|
+
const params = new URLSearchParams({
|
|
1057
|
+
client_id: githubClientId,
|
|
1058
|
+
redirect_uri: githubRedirectUri,
|
|
1059
|
+
scope: "user:email",
|
|
1060
|
+
state,
|
|
1061
|
+
allow_signup: "true"
|
|
1062
|
+
});
|
|
1063
|
+
const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
|
|
1064
|
+
res.redirect(url);
|
|
1065
|
+
});
|
|
1066
|
+
r.get("/github/callback", async (req, res) => {
|
|
1067
|
+
const githubClientId = process.env.GITHUB_CLIENT_ID;
|
|
1068
|
+
const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
|
|
1069
|
+
const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
|
|
1070
|
+
const githubDefaultRedirect = process.env.GITHUB_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
|
|
1071
|
+
if (!githubClientId || !githubClientSecret || !githubRedirectUri) {
|
|
1072
|
+
return res.status(500).json({ error: "GitHub login not configured" });
|
|
1073
|
+
}
|
|
1074
|
+
const code = String(req.query.code || "");
|
|
1075
|
+
const state = req.query.state ? String(req.query.state) : "";
|
|
1076
|
+
if (!code) {
|
|
1077
|
+
return res.status(400).json({ ok: false, error: "Missing GitHub code" });
|
|
1078
|
+
}
|
|
1079
|
+
try {
|
|
1080
|
+
const tokenRes = await fetch(
|
|
1081
|
+
"https://github.com/login/oauth/access_token",
|
|
1082
|
+
{
|
|
1083
|
+
method: "POST",
|
|
1084
|
+
headers: {
|
|
1085
|
+
Accept: "application/json",
|
|
1086
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1087
|
+
},
|
|
1088
|
+
body: new URLSearchParams({
|
|
1089
|
+
client_id: githubClientId,
|
|
1090
|
+
client_secret: githubClientSecret,
|
|
1091
|
+
code,
|
|
1092
|
+
redirect_uri: githubRedirectUri
|
|
1093
|
+
})
|
|
1094
|
+
}
|
|
1095
|
+
);
|
|
1096
|
+
const tokenJson = await tokenRes.json();
|
|
1097
|
+
if (!tokenJson.access_token) {
|
|
1098
|
+
console.error("GitHub token error:", tokenJson);
|
|
1099
|
+
return res.status(400).json({ ok: false, error: "Failed to get GitHub access token" });
|
|
1100
|
+
}
|
|
1101
|
+
const accessToken = tokenJson.access_token;
|
|
1102
|
+
const userRes = await fetch("https://api.github.com/user", {
|
|
1103
|
+
headers: {
|
|
1104
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1105
|
+
Accept: "application/vnd.github+json"
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
const githubUser = await userRes.json();
|
|
1109
|
+
const emailRes = await fetch("https://api.github.com/user/emails", {
|
|
1110
|
+
headers: {
|
|
1111
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1112
|
+
Accept: "application/vnd.github+json"
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
const emails = await emailRes.json();
|
|
1116
|
+
const primaryEmail = emails?.find(
|
|
1117
|
+
(e) => e.primary && e.verified
|
|
1118
|
+
)?.email;
|
|
1119
|
+
if (!primaryEmail) {
|
|
1120
|
+
return res.status(400).json({
|
|
1121
|
+
ok: false,
|
|
1122
|
+
error: "GitHub account has no verified email"
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
const firstName = githubUser.name?.split(" ")[0] || "";
|
|
1126
|
+
const lastName = githubUser.name?.split(" ").slice(1).join(" ") || "";
|
|
1127
|
+
let user = await OrgUser.findOne({ email: primaryEmail }).lean();
|
|
1128
|
+
if (!user) {
|
|
1129
|
+
const created = await OrgUser.create({
|
|
1130
|
+
email: primaryEmail,
|
|
1131
|
+
firstName,
|
|
1132
|
+
lastName,
|
|
1133
|
+
emailVerified: true,
|
|
1134
|
+
roles: ["platform_user"],
|
|
1135
|
+
projectId: null,
|
|
1136
|
+
metadata: [],
|
|
1137
|
+
githubId: githubUser.id
|
|
1138
|
+
});
|
|
1139
|
+
user = created.toObject();
|
|
1140
|
+
}
|
|
1141
|
+
const tokens = generateTokens(user);
|
|
1142
|
+
setAuthCookies(res, tokens, cookieConfig);
|
|
1143
|
+
const redirectTo = state ? decodeURIComponent(state) : githubDefaultRedirect;
|
|
1144
|
+
res.redirect(redirectTo);
|
|
1145
|
+
} catch (err) {
|
|
1146
|
+
console.error("GitHub callback error:", err);
|
|
1147
|
+
const redirectError = githubDefaultRedirect.includes("?") ? `${githubDefaultRedirect}&error=github_login_failed` : `${githubDefaultRedirect}?error=github_login_failed`;
|
|
1148
|
+
res.redirect(redirectError);
|
|
1149
|
+
}
|
|
972
1150
|
});
|
|
973
1151
|
r.get("/get-users", async (req, res) => {
|
|
974
1152
|
const user = await OrgUser.find({ projectId: req.query.projectId }).lean();
|
|
@@ -1046,6 +1224,11 @@ function generateTokens(user) {
|
|
|
1046
1224
|
orgId: user.orgId || null,
|
|
1047
1225
|
org_id: user.orgId || null,
|
|
1048
1226
|
projectId: user.projectId || null,
|
|
1227
|
+
firstName: user.firstName,
|
|
1228
|
+
lastName: user.lastName,
|
|
1229
|
+
emailVerified: user.emailVerified,
|
|
1230
|
+
createdAt: user.createdAt,
|
|
1231
|
+
metadata: user.metadata,
|
|
1049
1232
|
type: "user"
|
|
1050
1233
|
};
|
|
1051
1234
|
const accessToken = jwt4.sign(accessPayload, process.env.JWT_SECRET, {
|