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/index.js CHANGED
@@ -31,50 +31,6 @@ import express, {
31
31
  } from "express";
32
32
  import jwt4 from "jsonwebtoken";
33
33
 
34
- // src/core/utils.ts
35
- function hasRole(session, role) {
36
- if (!session || !session.roles) return false;
37
- return session.roles.includes(role);
38
- }
39
- function baseProjectCookieOptionsFrom(cookie) {
40
- const base = {
41
- secure: cookie.secure ?? false,
42
- sameSite: cookie.sameSite ?? "lax",
43
- path: cookie.path ?? "/",
44
- maxAge: cookie.maxAgeMs
45
- };
46
- if (cookie.domain) base.domain = cookie.domain;
47
- return base;
48
- }
49
- function hasAnyRole(session, roles) {
50
- if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
51
- return false;
52
- }
53
- return roles.some((role) => session.roles.includes(role));
54
- }
55
- function hasAllRoles(session, roles) {
56
- if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
57
- return false;
58
- }
59
- return roles.every((role) => session.roles.includes(role));
60
- }
61
- function hasPermission(session, permission) {
62
- if (!session || !session.permissions) return false;
63
- return session.permissions.includes(permission);
64
- }
65
- function hasAnyPermission(session, permissions) {
66
- if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
67
- return false;
68
- }
69
- return permissions.some((perm) => session.permissions.includes(perm));
70
- }
71
- function hasAllPermissions(session, permissions) {
72
- if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
73
- return false;
74
- }
75
- return permissions.every((perm) => session.permissions.includes(perm));
76
- }
77
-
78
34
  // src/config/loadConfig.ts
79
35
  function loadConfig() {
80
36
  return {
@@ -138,6 +94,63 @@ function isPlainObject(value) {
138
94
  return typeof value === "object" && value !== null && !Array.isArray(value);
139
95
  }
140
96
 
97
+ // src/core/utils.ts
98
+ function hasRole(session, role) {
99
+ if (!session || !session.roles) return false;
100
+ return session.roles.includes(role);
101
+ }
102
+ function baseProjectCookieOptionsFrom(cookie) {
103
+ const base = {
104
+ secure: cookie.secure ?? false,
105
+ sameSite: cookie.sameSite ?? "lax",
106
+ path: cookie.path ?? "/",
107
+ maxAge: cookie.maxAgeMs
108
+ };
109
+ if (cookie.domain) base.domain = cookie.domain;
110
+ return base;
111
+ }
112
+ function buildClearCookieOptions(cookie) {
113
+ const opts = {
114
+ httpOnly: true,
115
+ // not strictly required but fine
116
+ secure: cookie.secure ?? false,
117
+ sameSite: cookie.sameSite ?? "lax",
118
+ path: cookie.path ?? "/"
119
+ };
120
+ if (cookie.domain) {
121
+ opts.domain = cookie.domain;
122
+ }
123
+ return opts;
124
+ }
125
+ function hasAnyRole(session, roles) {
126
+ if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
127
+ return false;
128
+ }
129
+ return roles.some((role) => session.roles.includes(role));
130
+ }
131
+ function hasAllRoles(session, roles) {
132
+ if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
133
+ return false;
134
+ }
135
+ return roles.every((role) => session.roles.includes(role));
136
+ }
137
+ function hasPermission(session, permission) {
138
+ if (!session || !session.permissions) return false;
139
+ return session.permissions.includes(permission);
140
+ }
141
+ function hasAnyPermission(session, permissions) {
142
+ if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
143
+ return false;
144
+ }
145
+ return permissions.some((perm) => session.permissions.includes(perm));
146
+ }
147
+ function hasAllPermissions(session, permissions) {
148
+ if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
149
+ return false;
150
+ }
151
+ return permissions.every((perm) => session.permissions.includes(perm));
152
+ }
153
+
141
154
  // src/core/roles.config.ts
142
155
  var PLATFORM_ROLES = [
143
156
  {
@@ -182,10 +195,14 @@ function buildSession(payload) {
182
195
  roles: normalizedRoles,
183
196
  permissions
184
197
  };
198
+ if (payload?.firstName) session.firstName = payload.firstName;
199
+ if (payload?.lastName) session.lastName = payload.lastName;
185
200
  if (payload?.projectId) session.projectId = payload.projectId;
186
201
  if (payload?.orgId) session.orgId = payload.orgId;
187
202
  if (payload?.org_id) session.org_id = payload.org_id;
188
203
  if (payload?.authType) session.authType = payload.authType;
204
+ if (payload?.createdAt) session.createdAt = payload.createdAt;
205
+ if (payload?.metadata) session.metadata = payload.metadata;
189
206
  Object.keys(payload || {}).forEach((key) => {
190
207
  if (![
191
208
  "sub",
@@ -239,7 +256,7 @@ var MetadataSchema = new mongoose2.Schema(
239
256
  );
240
257
  var OrgUserSchema = new mongoose2.Schema(
241
258
  {
242
- id: { type: String, default: uuid(), index: true },
259
+ id: { type: String, default: uuid(), index: true, unique: true },
243
260
  email: { type: String, required: true, unique: true },
244
261
  firstName: { type: String, required: true },
245
262
  lastName: { type: String, required: true },
@@ -359,10 +376,14 @@ function requireAuth() {
359
376
  const session = buildSession({
360
377
  sub: user.id.toString(),
361
378
  email: user.email,
379
+ firstName: user.firstName,
380
+ lastName: user.lastName,
381
+ metadata: user.metadata || [],
362
382
  roles: user.roles || [],
363
383
  orgId: user.orgId,
364
384
  org_id: user.orgId,
365
- projectId: user.projectId
385
+ projectId: user.projectId,
386
+ createdAt: user.createdAt
366
387
  });
367
388
  session.authType = "api-key";
368
389
  session.projectId = readProjectId(req) || user.projectId || void 0;
@@ -543,14 +564,14 @@ var AuthAdminService = class {
543
564
  async createUserInRealm(payload) {
544
565
  const hashedPassword = payload.credentials?.[0]?.value ? await bcrypt.hash(payload.credentials[0].value, 10) : void 0;
545
566
  const user = await OrgUser.create({
546
- username: payload.username,
567
+ id: crypto.randomUUID(),
547
568
  email: payload.email,
548
569
  firstName: payload.firstName,
549
570
  lastName: payload.lastName,
550
571
  projectId: payload.projectId,
551
572
  emailVerified: payload.emailVerified || false,
552
573
  passwordHash: hashedPassword,
553
- enabled: true
574
+ metadata: payload.metadata || []
554
575
  });
555
576
  return user;
556
577
  }
@@ -638,29 +659,13 @@ var EmailService = class {
638
659
  }
639
660
  };
640
661
 
641
- // src/utils/cookie.ts
642
- function cookieOpts(isRefresh = false) {
643
- const maxAge = isRefresh ? config.cookies.refreshTtlMs : config.cookies.accessTtlMs;
644
- const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
645
- return {
646
- httpOnly: true,
647
- secure,
648
- sameSite: "none",
649
- domain: process.env.COOKIE_DOMAIN,
650
- maxAge
651
- };
652
- }
653
- function clearOpts() {
654
- const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
655
- return {
656
- domain: process.env.COOKIE_DOMAIN,
657
- sameSite: "none",
658
- secure
659
- };
660
- }
661
-
662
662
  // src/express/auth.routes.ts
663
663
  function createAuthRouter(options = {}) {
664
+ const googleClientId = process.env.GOOGLE_CLIENT_ID;
665
+ const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
666
+ const googleRedirectUri = process.env.GOOGLE_REDIRECT_URI;
667
+ const googleDefaultRedirect = process.env.GOOGLE_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
668
+ const isGoogleEnabled = !!googleClientId && !!googleClientSecret && !!googleRedirectUri;
664
669
  if (options.config) {
665
670
  configureAuthX(options.config);
666
671
  }
@@ -685,8 +690,10 @@ function createAuthRouter(options = {}) {
685
690
  );
686
691
  r.post("/login", validateLogin, async (req, res) => {
687
692
  const { email: emailAddress, password } = req.body || {};
693
+ console.log(emailAddress, password, "body");
688
694
  try {
689
695
  const user = await OrgUser.findOne({ email: emailAddress }).select("+password").lean();
696
+ console.log(user, "user");
690
697
  if (!user) {
691
698
  return res.status(400).json({
692
699
  error: "Invalid email or password",
@@ -739,13 +746,13 @@ function createAuthRouter(options = {}) {
739
746
  firstName,
740
747
  lastName,
741
748
  projectId,
742
- credentials: [{ type: "password", value: password, temporary: false }]
749
+ credentials: [{ type: "password", value: password, temporary: false }],
750
+ metadata
743
751
  });
744
752
  await authAdmin.assignRealmRole(kcUser.id, "platform_user");
745
753
  const user = await OrgUser.findOneAndUpdate(
746
754
  { email: kcUser.email },
747
755
  {
748
- id: kcUser.id,
749
756
  email: kcUser.email,
750
757
  firstName,
751
758
  lastName,
@@ -785,8 +792,9 @@ function createAuthRouter(options = {}) {
785
792
  return res.json(req.user || null);
786
793
  });
787
794
  r.post("/logout", async (_req, res) => {
788
- res.clearCookie("access_token", clearOpts());
789
- res.clearCookie("refresh_token", clearOpts());
795
+ const clearOptions = buildClearCookieOptions(cookieConfig);
796
+ res.clearCookie("access_token", clearOptions);
797
+ res.clearCookie("refresh_token", clearOptions);
790
798
  res.json({ ok: true });
791
799
  });
792
800
  r.put("/:userId/metadata", requireAuth(), async (req, res) => {
@@ -1025,16 +1033,186 @@ function createAuthRouter(options = {}) {
1025
1033
  const user = await OrgUser.findOne({ email: req.query.email }).lean();
1026
1034
  res.json(user || null);
1027
1035
  });
1028
- r.get("/google", async (_req, res) => {
1029
- res.json({ url: "/auth/google/callback?code=demo" });
1036
+ r.get("/google", (req, res) => {
1037
+ if (!isGoogleEnabled) {
1038
+ return res.status(500).json({ error: "Google login not configured" });
1039
+ }
1040
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
1041
+ const params = new URLSearchParams({
1042
+ client_id: googleClientId,
1043
+ redirect_uri: googleRedirectUri,
1044
+ response_type: "code",
1045
+ scope: "openid email profile",
1046
+ access_type: "offline",
1047
+ prompt: "consent",
1048
+ state
1049
+ });
1050
+ const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
1051
+ res.redirect(url);
1030
1052
  });
1031
- r.get("/google/callback", async (_req, res) => {
1032
- res.cookie(
1033
- "access_token",
1034
- "ACCESS.TOKEN.PLACEHOLDER",
1035
- cookieOpts(false)
1036
- );
1037
- res.redirect("/");
1053
+ r.get("/google/callback", async (req, res) => {
1054
+ if (!isGoogleEnabled) {
1055
+ return res.status(500).json({ error: "Google login not configured" });
1056
+ }
1057
+ const code = String(req.query.code || "");
1058
+ const state = req.query.state ? String(req.query.state) : "";
1059
+ if (!code) {
1060
+ return res.status(400).json({ ok: false, error: "Missing authorization code" });
1061
+ }
1062
+ try {
1063
+ const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
1064
+ method: "POST",
1065
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1066
+ body: new URLSearchParams({
1067
+ code,
1068
+ client_id: googleClientId,
1069
+ client_secret: googleClientSecret,
1070
+ redirect_uri: googleRedirectUri,
1071
+ grant_type: "authorization_code"
1072
+ })
1073
+ });
1074
+ if (!tokenRes.ok) {
1075
+ const errJson = await tokenRes.json().catch(() => ({}));
1076
+ console.error("Google token error", errJson);
1077
+ return res.status(400).json({ ok: false, error: "Failed to exchange Google code" });
1078
+ }
1079
+ const tokenJson = await tokenRes.json();
1080
+ if (!tokenJson.id_token) {
1081
+ return res.status(400).json({ ok: false, error: "Missing id_token from Google" });
1082
+ }
1083
+ const decoded = jwt4.decode(tokenJson.id_token);
1084
+ const email2 = decoded?.email;
1085
+ if (!email2) {
1086
+ return res.status(400).json({ ok: false, error: "Google account has no email" });
1087
+ }
1088
+ const emailVerified = decoded.email_verified ?? true;
1089
+ const firstName = decoded.given_name || "";
1090
+ const lastName = decoded.family_name || "";
1091
+ let user = await OrgUser.findOne({ email: email2 }).lean();
1092
+ if (!user) {
1093
+ const created = await OrgUser.create({
1094
+ email: email2,
1095
+ firstName,
1096
+ lastName,
1097
+ emailVerified,
1098
+ roles: ["platform_user"],
1099
+ projectId: null,
1100
+ metadata: []
1101
+ // you can also store googleId: decoded.sub
1102
+ });
1103
+ user = created.toObject();
1104
+ }
1105
+ const tokens = generateTokens(user);
1106
+ setAuthCookies(res, tokens, cookieConfig);
1107
+ const redirectTo = state ? decodeURIComponent(state) : googleDefaultRedirect;
1108
+ res.redirect(redirectTo);
1109
+ } catch (err) {
1110
+ console.error("Google callback error", err);
1111
+ const redirectError = googleDefaultRedirect.includes("?") ? `${googleDefaultRedirect}&error=google_login_failed` : `${googleDefaultRedirect}?error=google_login_failed`;
1112
+ res.redirect(redirectError);
1113
+ }
1114
+ });
1115
+ r.get("/github", (req, res) => {
1116
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1117
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1118
+ if (!githubClientId || !githubRedirectUri) {
1119
+ return res.status(500).json({ error: "GitHub login not configured" });
1120
+ }
1121
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
1122
+ const params = new URLSearchParams({
1123
+ client_id: githubClientId,
1124
+ redirect_uri: githubRedirectUri,
1125
+ scope: "user:email",
1126
+ state,
1127
+ allow_signup: "true"
1128
+ });
1129
+ const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
1130
+ res.redirect(url);
1131
+ });
1132
+ r.get("/github/callback", async (req, res) => {
1133
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1134
+ const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
1135
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1136
+ const githubDefaultRedirect = process.env.GITHUB_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
1137
+ if (!githubClientId || !githubClientSecret || !githubRedirectUri) {
1138
+ return res.status(500).json({ error: "GitHub login not configured" });
1139
+ }
1140
+ const code = String(req.query.code || "");
1141
+ const state = req.query.state ? String(req.query.state) : "";
1142
+ if (!code) {
1143
+ return res.status(400).json({ ok: false, error: "Missing GitHub code" });
1144
+ }
1145
+ try {
1146
+ const tokenRes = await fetch(
1147
+ "https://github.com/login/oauth/access_token",
1148
+ {
1149
+ method: "POST",
1150
+ headers: {
1151
+ Accept: "application/json",
1152
+ "Content-Type": "application/x-www-form-urlencoded"
1153
+ },
1154
+ body: new URLSearchParams({
1155
+ client_id: githubClientId,
1156
+ client_secret: githubClientSecret,
1157
+ code,
1158
+ redirect_uri: githubRedirectUri
1159
+ })
1160
+ }
1161
+ );
1162
+ const tokenJson = await tokenRes.json();
1163
+ if (!tokenJson.access_token) {
1164
+ console.error("GitHub token error:", tokenJson);
1165
+ return res.status(400).json({ ok: false, error: "Failed to get GitHub access token" });
1166
+ }
1167
+ const accessToken = tokenJson.access_token;
1168
+ const userRes = await fetch("https://api.github.com/user", {
1169
+ headers: {
1170
+ Authorization: `Bearer ${accessToken}`,
1171
+ Accept: "application/vnd.github+json"
1172
+ }
1173
+ });
1174
+ const githubUser = await userRes.json();
1175
+ const emailRes = await fetch("https://api.github.com/user/emails", {
1176
+ headers: {
1177
+ Authorization: `Bearer ${accessToken}`,
1178
+ Accept: "application/vnd.github+json"
1179
+ }
1180
+ });
1181
+ const emails = await emailRes.json();
1182
+ const primaryEmail = emails?.find(
1183
+ (e) => e.primary && e.verified
1184
+ )?.email;
1185
+ if (!primaryEmail) {
1186
+ return res.status(400).json({
1187
+ ok: false,
1188
+ error: "GitHub account has no verified email"
1189
+ });
1190
+ }
1191
+ const firstName = githubUser.name?.split(" ")[0] || "";
1192
+ const lastName = githubUser.name?.split(" ").slice(1).join(" ") || "";
1193
+ let user = await OrgUser.findOne({ email: primaryEmail }).lean();
1194
+ if (!user) {
1195
+ const created = await OrgUser.create({
1196
+ email: primaryEmail,
1197
+ firstName,
1198
+ lastName,
1199
+ emailVerified: true,
1200
+ roles: ["platform_user"],
1201
+ projectId: null,
1202
+ metadata: [],
1203
+ githubId: githubUser.id
1204
+ });
1205
+ user = created.toObject();
1206
+ }
1207
+ const tokens = generateTokens(user);
1208
+ setAuthCookies(res, tokens, cookieConfig);
1209
+ const redirectTo = state ? decodeURIComponent(state) : githubDefaultRedirect;
1210
+ res.redirect(redirectTo);
1211
+ } catch (err) {
1212
+ console.error("GitHub callback error:", err);
1213
+ const redirectError = githubDefaultRedirect.includes("?") ? `${githubDefaultRedirect}&error=github_login_failed` : `${githubDefaultRedirect}?error=github_login_failed`;
1214
+ res.redirect(redirectError);
1215
+ }
1038
1216
  });
1039
1217
  r.get("/get-users", async (req, res) => {
1040
1218
  const user = await OrgUser.find({ projectId: req.query.projectId }).lean();
@@ -1112,6 +1290,11 @@ function generateTokens(user) {
1112
1290
  orgId: user.orgId || null,
1113
1291
  org_id: user.orgId || null,
1114
1292
  projectId: user.projectId || null,
1293
+ firstName: user.firstName,
1294
+ lastName: user.lastName,
1295
+ emailVerified: user.emailVerified,
1296
+ createdAt: user.createdAt,
1297
+ metadata: user.metadata,
1115
1298
  type: "user"
1116
1299
  };
1117
1300
  const accessToken = jwt4.sign(accessPayload, process.env.JWT_SECRET, {