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.cjs
CHANGED
|
@@ -44,24 +44,6 @@ var import_crypto = require("crypto");
|
|
|
44
44
|
var import_express = __toESM(require("express"), 1);
|
|
45
45
|
var import_jsonwebtoken4 = __toESM(require("jsonwebtoken"), 1);
|
|
46
46
|
|
|
47
|
-
// src/core/utils.ts
|
|
48
|
-
function baseProjectCookieOptionsFrom(cookie) {
|
|
49
|
-
const base = {
|
|
50
|
-
secure: cookie.secure ?? false,
|
|
51
|
-
sameSite: cookie.sameSite ?? "lax",
|
|
52
|
-
path: cookie.path ?? "/",
|
|
53
|
-
maxAge: cookie.maxAgeMs
|
|
54
|
-
};
|
|
55
|
-
if (cookie.domain) base.domain = cookie.domain;
|
|
56
|
-
return base;
|
|
57
|
-
}
|
|
58
|
-
function hasAnyRole(session, roles) {
|
|
59
|
-
if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
return roles.some((role) => session.roles.includes(role));
|
|
63
|
-
}
|
|
64
|
-
|
|
65
47
|
// src/config/loadConfig.ts
|
|
66
48
|
function loadConfig() {
|
|
67
49
|
return {
|
|
@@ -125,6 +107,37 @@ function isPlainObject(value) {
|
|
|
125
107
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
126
108
|
}
|
|
127
109
|
|
|
110
|
+
// src/core/utils.ts
|
|
111
|
+
function baseProjectCookieOptionsFrom(cookie) {
|
|
112
|
+
const base = {
|
|
113
|
+
secure: cookie.secure ?? false,
|
|
114
|
+
sameSite: cookie.sameSite ?? "lax",
|
|
115
|
+
path: cookie.path ?? "/",
|
|
116
|
+
maxAge: cookie.maxAgeMs
|
|
117
|
+
};
|
|
118
|
+
if (cookie.domain) base.domain = cookie.domain;
|
|
119
|
+
return base;
|
|
120
|
+
}
|
|
121
|
+
function buildClearCookieOptions(cookie) {
|
|
122
|
+
const opts = {
|
|
123
|
+
httpOnly: true,
|
|
124
|
+
// not strictly required but fine
|
|
125
|
+
secure: cookie.secure ?? false,
|
|
126
|
+
sameSite: cookie.sameSite ?? "lax",
|
|
127
|
+
path: cookie.path ?? "/"
|
|
128
|
+
};
|
|
129
|
+
if (cookie.domain) {
|
|
130
|
+
opts.domain = cookie.domain;
|
|
131
|
+
}
|
|
132
|
+
return opts;
|
|
133
|
+
}
|
|
134
|
+
function hasAnyRole(session, roles) {
|
|
135
|
+
if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return roles.some((role) => session.roles.includes(role));
|
|
139
|
+
}
|
|
140
|
+
|
|
128
141
|
// src/core/roles.config.ts
|
|
129
142
|
var PLATFORM_ROLES = [
|
|
130
143
|
{
|
|
@@ -169,10 +182,14 @@ function buildSession(payload) {
|
|
|
169
182
|
roles: normalizedRoles,
|
|
170
183
|
permissions
|
|
171
184
|
};
|
|
185
|
+
if (payload?.firstName) session.firstName = payload.firstName;
|
|
186
|
+
if (payload?.lastName) session.lastName = payload.lastName;
|
|
172
187
|
if (payload?.projectId) session.projectId = payload.projectId;
|
|
173
188
|
if (payload?.orgId) session.orgId = payload.orgId;
|
|
174
189
|
if (payload?.org_id) session.org_id = payload.org_id;
|
|
175
190
|
if (payload?.authType) session.authType = payload.authType;
|
|
191
|
+
if (payload?.createdAt) session.createdAt = payload.createdAt;
|
|
192
|
+
if (payload?.metadata) session.metadata = payload.metadata;
|
|
176
193
|
Object.keys(payload || {}).forEach((key) => {
|
|
177
194
|
if (![
|
|
178
195
|
"sub",
|
|
@@ -226,7 +243,7 @@ var MetadataSchema = new import_mongoose2.default.Schema(
|
|
|
226
243
|
);
|
|
227
244
|
var OrgUserSchema = new import_mongoose2.default.Schema(
|
|
228
245
|
{
|
|
229
|
-
id: { type: String, default: (0, import_uuid.v4)(), index: true },
|
|
246
|
+
id: { type: String, default: (0, import_uuid.v4)(), index: true, unique: true },
|
|
230
247
|
email: { type: String, required: true, unique: true },
|
|
231
248
|
firstName: { type: String, required: true },
|
|
232
249
|
lastName: { type: String, required: true },
|
|
@@ -346,10 +363,14 @@ function requireAuth() {
|
|
|
346
363
|
const session = buildSession({
|
|
347
364
|
sub: user.id.toString(),
|
|
348
365
|
email: user.email,
|
|
366
|
+
firstName: user.firstName,
|
|
367
|
+
lastName: user.lastName,
|
|
368
|
+
metadata: user.metadata || [],
|
|
349
369
|
roles: user.roles || [],
|
|
350
370
|
orgId: user.orgId,
|
|
351
371
|
org_id: user.orgId,
|
|
352
|
-
projectId: user.projectId
|
|
372
|
+
projectId: user.projectId,
|
|
373
|
+
createdAt: user.createdAt
|
|
353
374
|
});
|
|
354
375
|
session.authType = "api-key";
|
|
355
376
|
session.projectId = readProjectId(req) || user.projectId || void 0;
|
|
@@ -515,14 +536,14 @@ var AuthAdminService = class {
|
|
|
515
536
|
async createUserInRealm(payload) {
|
|
516
537
|
const hashedPassword = payload.credentials?.[0]?.value ? await import_bcrypt.default.hash(payload.credentials[0].value, 10) : void 0;
|
|
517
538
|
const user = await OrgUser.create({
|
|
518
|
-
|
|
539
|
+
id: crypto.randomUUID(),
|
|
519
540
|
email: payload.email,
|
|
520
541
|
firstName: payload.firstName,
|
|
521
542
|
lastName: payload.lastName,
|
|
522
543
|
projectId: payload.projectId,
|
|
523
544
|
emailVerified: payload.emailVerified || false,
|
|
524
545
|
passwordHash: hashedPassword,
|
|
525
|
-
|
|
546
|
+
metadata: payload.metadata || []
|
|
526
547
|
});
|
|
527
548
|
return user;
|
|
528
549
|
}
|
|
@@ -610,29 +631,13 @@ var EmailService = class {
|
|
|
610
631
|
}
|
|
611
632
|
};
|
|
612
633
|
|
|
613
|
-
// src/utils/cookie.ts
|
|
614
|
-
function cookieOpts(isRefresh = false) {
|
|
615
|
-
const maxAge = isRefresh ? config.cookies.refreshTtlMs : config.cookies.accessTtlMs;
|
|
616
|
-
const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
|
|
617
|
-
return {
|
|
618
|
-
httpOnly: true,
|
|
619
|
-
secure,
|
|
620
|
-
sameSite: "none",
|
|
621
|
-
domain: process.env.COOKIE_DOMAIN,
|
|
622
|
-
maxAge
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
function clearOpts() {
|
|
626
|
-
const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
|
|
627
|
-
return {
|
|
628
|
-
domain: process.env.COOKIE_DOMAIN,
|
|
629
|
-
sameSite: "none",
|
|
630
|
-
secure
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
|
|
634
634
|
// src/express/auth.routes.ts
|
|
635
635
|
function createAuthRouter(options = {}) {
|
|
636
|
+
const googleClientId = process.env.GOOGLE_CLIENT_ID;
|
|
637
|
+
const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
638
|
+
const googleRedirectUri = process.env.GOOGLE_REDIRECT_URI;
|
|
639
|
+
const googleDefaultRedirect = process.env.GOOGLE_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
|
|
640
|
+
const isGoogleEnabled = !!googleClientId && !!googleClientSecret && !!googleRedirectUri;
|
|
636
641
|
if (options.config) {
|
|
637
642
|
configureAuthX(options.config);
|
|
638
643
|
}
|
|
@@ -657,8 +662,10 @@ function createAuthRouter(options = {}) {
|
|
|
657
662
|
);
|
|
658
663
|
r.post("/login", validateLogin, async (req, res) => {
|
|
659
664
|
const { email: emailAddress, password } = req.body || {};
|
|
665
|
+
console.log(emailAddress, password, "body");
|
|
660
666
|
try {
|
|
661
667
|
const user = await OrgUser.findOne({ email: emailAddress }).select("+password").lean();
|
|
668
|
+
console.log(user, "user");
|
|
662
669
|
if (!user) {
|
|
663
670
|
return res.status(400).json({
|
|
664
671
|
error: "Invalid email or password",
|
|
@@ -711,13 +718,13 @@ function createAuthRouter(options = {}) {
|
|
|
711
718
|
firstName,
|
|
712
719
|
lastName,
|
|
713
720
|
projectId,
|
|
714
|
-
credentials: [{ type: "password", value: password, temporary: false }]
|
|
721
|
+
credentials: [{ type: "password", value: password, temporary: false }],
|
|
722
|
+
metadata
|
|
715
723
|
});
|
|
716
724
|
await authAdmin.assignRealmRole(kcUser.id, "platform_user");
|
|
717
725
|
const user = await OrgUser.findOneAndUpdate(
|
|
718
726
|
{ email: kcUser.email },
|
|
719
727
|
{
|
|
720
|
-
id: kcUser.id,
|
|
721
728
|
email: kcUser.email,
|
|
722
729
|
firstName,
|
|
723
730
|
lastName,
|
|
@@ -757,8 +764,9 @@ function createAuthRouter(options = {}) {
|
|
|
757
764
|
return res.json(req.user || null);
|
|
758
765
|
});
|
|
759
766
|
r.post("/logout", async (_req, res) => {
|
|
760
|
-
|
|
761
|
-
res.clearCookie("
|
|
767
|
+
const clearOptions = buildClearCookieOptions(cookieConfig);
|
|
768
|
+
res.clearCookie("access_token", clearOptions);
|
|
769
|
+
res.clearCookie("refresh_token", clearOptions);
|
|
762
770
|
res.json({ ok: true });
|
|
763
771
|
});
|
|
764
772
|
r.put("/:userId/metadata", requireAuth(), async (req, res) => {
|
|
@@ -997,16 +1005,186 @@ function createAuthRouter(options = {}) {
|
|
|
997
1005
|
const user = await OrgUser.findOne({ email: req.query.email }).lean();
|
|
998
1006
|
res.json(user || null);
|
|
999
1007
|
});
|
|
1000
|
-
r.get("/google",
|
|
1001
|
-
|
|
1008
|
+
r.get("/google", (req, res) => {
|
|
1009
|
+
if (!isGoogleEnabled) {
|
|
1010
|
+
return res.status(500).json({ error: "Google login not configured" });
|
|
1011
|
+
}
|
|
1012
|
+
const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
|
|
1013
|
+
const params = new URLSearchParams({
|
|
1014
|
+
client_id: googleClientId,
|
|
1015
|
+
redirect_uri: googleRedirectUri,
|
|
1016
|
+
response_type: "code",
|
|
1017
|
+
scope: "openid email profile",
|
|
1018
|
+
access_type: "offline",
|
|
1019
|
+
prompt: "consent",
|
|
1020
|
+
state
|
|
1021
|
+
});
|
|
1022
|
+
const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
1023
|
+
res.redirect(url);
|
|
1002
1024
|
});
|
|
1003
|
-
r.get("/google/callback", async (
|
|
1004
|
-
|
|
1005
|
-
"
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
);
|
|
1009
|
-
|
|
1025
|
+
r.get("/google/callback", async (req, res) => {
|
|
1026
|
+
if (!isGoogleEnabled) {
|
|
1027
|
+
return res.status(500).json({ error: "Google login not configured" });
|
|
1028
|
+
}
|
|
1029
|
+
const code = String(req.query.code || "");
|
|
1030
|
+
const state = req.query.state ? String(req.query.state) : "";
|
|
1031
|
+
if (!code) {
|
|
1032
|
+
return res.status(400).json({ ok: false, error: "Missing authorization code" });
|
|
1033
|
+
}
|
|
1034
|
+
try {
|
|
1035
|
+
const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
|
|
1036
|
+
method: "POST",
|
|
1037
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1038
|
+
body: new URLSearchParams({
|
|
1039
|
+
code,
|
|
1040
|
+
client_id: googleClientId,
|
|
1041
|
+
client_secret: googleClientSecret,
|
|
1042
|
+
redirect_uri: googleRedirectUri,
|
|
1043
|
+
grant_type: "authorization_code"
|
|
1044
|
+
})
|
|
1045
|
+
});
|
|
1046
|
+
if (!tokenRes.ok) {
|
|
1047
|
+
const errJson = await tokenRes.json().catch(() => ({}));
|
|
1048
|
+
console.error("Google token error", errJson);
|
|
1049
|
+
return res.status(400).json({ ok: false, error: "Failed to exchange Google code" });
|
|
1050
|
+
}
|
|
1051
|
+
const tokenJson = await tokenRes.json();
|
|
1052
|
+
if (!tokenJson.id_token) {
|
|
1053
|
+
return res.status(400).json({ ok: false, error: "Missing id_token from Google" });
|
|
1054
|
+
}
|
|
1055
|
+
const decoded = import_jsonwebtoken4.default.decode(tokenJson.id_token);
|
|
1056
|
+
const email2 = decoded?.email;
|
|
1057
|
+
if (!email2) {
|
|
1058
|
+
return res.status(400).json({ ok: false, error: "Google account has no email" });
|
|
1059
|
+
}
|
|
1060
|
+
const emailVerified = decoded.email_verified ?? true;
|
|
1061
|
+
const firstName = decoded.given_name || "";
|
|
1062
|
+
const lastName = decoded.family_name || "";
|
|
1063
|
+
let user = await OrgUser.findOne({ email: email2 }).lean();
|
|
1064
|
+
if (!user) {
|
|
1065
|
+
const created = await OrgUser.create({
|
|
1066
|
+
email: email2,
|
|
1067
|
+
firstName,
|
|
1068
|
+
lastName,
|
|
1069
|
+
emailVerified,
|
|
1070
|
+
roles: ["platform_user"],
|
|
1071
|
+
projectId: null,
|
|
1072
|
+
metadata: []
|
|
1073
|
+
// you can also store googleId: decoded.sub
|
|
1074
|
+
});
|
|
1075
|
+
user = created.toObject();
|
|
1076
|
+
}
|
|
1077
|
+
const tokens = generateTokens(user);
|
|
1078
|
+
setAuthCookies(res, tokens, cookieConfig);
|
|
1079
|
+
const redirectTo = state ? decodeURIComponent(state) : googleDefaultRedirect;
|
|
1080
|
+
res.redirect(redirectTo);
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
console.error("Google callback error", err);
|
|
1083
|
+
const redirectError = googleDefaultRedirect.includes("?") ? `${googleDefaultRedirect}&error=google_login_failed` : `${googleDefaultRedirect}?error=google_login_failed`;
|
|
1084
|
+
res.redirect(redirectError);
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
r.get("/github", (req, res) => {
|
|
1088
|
+
const githubClientId = process.env.GITHUB_CLIENT_ID;
|
|
1089
|
+
const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
|
|
1090
|
+
if (!githubClientId || !githubRedirectUri) {
|
|
1091
|
+
return res.status(500).json({ error: "GitHub login not configured" });
|
|
1092
|
+
}
|
|
1093
|
+
const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
|
|
1094
|
+
const params = new URLSearchParams({
|
|
1095
|
+
client_id: githubClientId,
|
|
1096
|
+
redirect_uri: githubRedirectUri,
|
|
1097
|
+
scope: "user:email",
|
|
1098
|
+
state,
|
|
1099
|
+
allow_signup: "true"
|
|
1100
|
+
});
|
|
1101
|
+
const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
|
|
1102
|
+
res.redirect(url);
|
|
1103
|
+
});
|
|
1104
|
+
r.get("/github/callback", async (req, res) => {
|
|
1105
|
+
const githubClientId = process.env.GITHUB_CLIENT_ID;
|
|
1106
|
+
const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
|
|
1107
|
+
const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
|
|
1108
|
+
const githubDefaultRedirect = process.env.GITHUB_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
|
|
1109
|
+
if (!githubClientId || !githubClientSecret || !githubRedirectUri) {
|
|
1110
|
+
return res.status(500).json({ error: "GitHub login not configured" });
|
|
1111
|
+
}
|
|
1112
|
+
const code = String(req.query.code || "");
|
|
1113
|
+
const state = req.query.state ? String(req.query.state) : "";
|
|
1114
|
+
if (!code) {
|
|
1115
|
+
return res.status(400).json({ ok: false, error: "Missing GitHub code" });
|
|
1116
|
+
}
|
|
1117
|
+
try {
|
|
1118
|
+
const tokenRes = await fetch(
|
|
1119
|
+
"https://github.com/login/oauth/access_token",
|
|
1120
|
+
{
|
|
1121
|
+
method: "POST",
|
|
1122
|
+
headers: {
|
|
1123
|
+
Accept: "application/json",
|
|
1124
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
1125
|
+
},
|
|
1126
|
+
body: new URLSearchParams({
|
|
1127
|
+
client_id: githubClientId,
|
|
1128
|
+
client_secret: githubClientSecret,
|
|
1129
|
+
code,
|
|
1130
|
+
redirect_uri: githubRedirectUri
|
|
1131
|
+
})
|
|
1132
|
+
}
|
|
1133
|
+
);
|
|
1134
|
+
const tokenJson = await tokenRes.json();
|
|
1135
|
+
if (!tokenJson.access_token) {
|
|
1136
|
+
console.error("GitHub token error:", tokenJson);
|
|
1137
|
+
return res.status(400).json({ ok: false, error: "Failed to get GitHub access token" });
|
|
1138
|
+
}
|
|
1139
|
+
const accessToken = tokenJson.access_token;
|
|
1140
|
+
const userRes = await fetch("https://api.github.com/user", {
|
|
1141
|
+
headers: {
|
|
1142
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1143
|
+
Accept: "application/vnd.github+json"
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
const githubUser = await userRes.json();
|
|
1147
|
+
const emailRes = await fetch("https://api.github.com/user/emails", {
|
|
1148
|
+
headers: {
|
|
1149
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1150
|
+
Accept: "application/vnd.github+json"
|
|
1151
|
+
}
|
|
1152
|
+
});
|
|
1153
|
+
const emails = await emailRes.json();
|
|
1154
|
+
const primaryEmail = emails?.find(
|
|
1155
|
+
(e) => e.primary && e.verified
|
|
1156
|
+
)?.email;
|
|
1157
|
+
if (!primaryEmail) {
|
|
1158
|
+
return res.status(400).json({
|
|
1159
|
+
ok: false,
|
|
1160
|
+
error: "GitHub account has no verified email"
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
const firstName = githubUser.name?.split(" ")[0] || "";
|
|
1164
|
+
const lastName = githubUser.name?.split(" ").slice(1).join(" ") || "";
|
|
1165
|
+
let user = await OrgUser.findOne({ email: primaryEmail }).lean();
|
|
1166
|
+
if (!user) {
|
|
1167
|
+
const created = await OrgUser.create({
|
|
1168
|
+
email: primaryEmail,
|
|
1169
|
+
firstName,
|
|
1170
|
+
lastName,
|
|
1171
|
+
emailVerified: true,
|
|
1172
|
+
roles: ["platform_user"],
|
|
1173
|
+
projectId: null,
|
|
1174
|
+
metadata: [],
|
|
1175
|
+
githubId: githubUser.id
|
|
1176
|
+
});
|
|
1177
|
+
user = created.toObject();
|
|
1178
|
+
}
|
|
1179
|
+
const tokens = generateTokens(user);
|
|
1180
|
+
setAuthCookies(res, tokens, cookieConfig);
|
|
1181
|
+
const redirectTo = state ? decodeURIComponent(state) : githubDefaultRedirect;
|
|
1182
|
+
res.redirect(redirectTo);
|
|
1183
|
+
} catch (err) {
|
|
1184
|
+
console.error("GitHub callback error:", err);
|
|
1185
|
+
const redirectError = githubDefaultRedirect.includes("?") ? `${githubDefaultRedirect}&error=github_login_failed` : `${githubDefaultRedirect}?error=github_login_failed`;
|
|
1186
|
+
res.redirect(redirectError);
|
|
1187
|
+
}
|
|
1010
1188
|
});
|
|
1011
1189
|
r.get("/get-users", async (req, res) => {
|
|
1012
1190
|
const user = await OrgUser.find({ projectId: req.query.projectId }).lean();
|
|
@@ -1084,6 +1262,11 @@ function generateTokens(user) {
|
|
|
1084
1262
|
orgId: user.orgId || null,
|
|
1085
1263
|
org_id: user.orgId || null,
|
|
1086
1264
|
projectId: user.projectId || null,
|
|
1265
|
+
firstName: user.firstName,
|
|
1266
|
+
lastName: user.lastName,
|
|
1267
|
+
emailVerified: user.emailVerified,
|
|
1268
|
+
createdAt: user.createdAt,
|
|
1269
|
+
metadata: user.metadata,
|
|
1087
1270
|
type: "user"
|
|
1088
1271
|
};
|
|
1089
1272
|
const accessToken = import_jsonwebtoken4.default.sign(accessPayload, process.env.JWT_SECRET, {
|