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.cjs CHANGED
@@ -83,50 +83,6 @@ var import_crypto = require("crypto");
83
83
  var import_express = __toESM(require("express"), 1);
84
84
  var import_jsonwebtoken4 = __toESM(require("jsonwebtoken"), 1);
85
85
 
86
- // src/core/utils.ts
87
- function hasRole(session, role) {
88
- if (!session || !session.roles) return false;
89
- return session.roles.includes(role);
90
- }
91
- function baseProjectCookieOptionsFrom(cookie) {
92
- const base = {
93
- secure: cookie.secure ?? false,
94
- sameSite: cookie.sameSite ?? "lax",
95
- path: cookie.path ?? "/",
96
- maxAge: cookie.maxAgeMs
97
- };
98
- if (cookie.domain) base.domain = cookie.domain;
99
- return base;
100
- }
101
- function hasAnyRole(session, roles) {
102
- if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
103
- return false;
104
- }
105
- return roles.some((role) => session.roles.includes(role));
106
- }
107
- function hasAllRoles(session, roles) {
108
- if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
109
- return false;
110
- }
111
- return roles.every((role) => session.roles.includes(role));
112
- }
113
- function hasPermission(session, permission) {
114
- if (!session || !session.permissions) return false;
115
- return session.permissions.includes(permission);
116
- }
117
- function hasAnyPermission(session, permissions) {
118
- if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
119
- return false;
120
- }
121
- return permissions.some((perm) => session.permissions.includes(perm));
122
- }
123
- function hasAllPermissions(session, permissions) {
124
- if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
125
- return false;
126
- }
127
- return permissions.every((perm) => session.permissions.includes(perm));
128
- }
129
-
130
86
  // src/config/loadConfig.ts
131
87
  function loadConfig() {
132
88
  return {
@@ -190,6 +146,63 @@ function isPlainObject(value) {
190
146
  return typeof value === "object" && value !== null && !Array.isArray(value);
191
147
  }
192
148
 
149
+ // src/core/utils.ts
150
+ function hasRole(session, role) {
151
+ if (!session || !session.roles) return false;
152
+ return session.roles.includes(role);
153
+ }
154
+ function baseProjectCookieOptionsFrom(cookie) {
155
+ const base = {
156
+ secure: cookie.secure ?? false,
157
+ sameSite: cookie.sameSite ?? "lax",
158
+ path: cookie.path ?? "/",
159
+ maxAge: cookie.maxAgeMs
160
+ };
161
+ if (cookie.domain) base.domain = cookie.domain;
162
+ return base;
163
+ }
164
+ function buildClearCookieOptions(cookie) {
165
+ const opts = {
166
+ httpOnly: true,
167
+ // not strictly required but fine
168
+ secure: cookie.secure ?? false,
169
+ sameSite: cookie.sameSite ?? "lax",
170
+ path: cookie.path ?? "/"
171
+ };
172
+ if (cookie.domain) {
173
+ opts.domain = cookie.domain;
174
+ }
175
+ return opts;
176
+ }
177
+ function hasAnyRole(session, roles) {
178
+ if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
179
+ return false;
180
+ }
181
+ return roles.some((role) => session.roles.includes(role));
182
+ }
183
+ function hasAllRoles(session, roles) {
184
+ if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
185
+ return false;
186
+ }
187
+ return roles.every((role) => session.roles.includes(role));
188
+ }
189
+ function hasPermission(session, permission) {
190
+ if (!session || !session.permissions) return false;
191
+ return session.permissions.includes(permission);
192
+ }
193
+ function hasAnyPermission(session, permissions) {
194
+ if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
195
+ return false;
196
+ }
197
+ return permissions.some((perm) => session.permissions.includes(perm));
198
+ }
199
+ function hasAllPermissions(session, permissions) {
200
+ if (!session || !session.permissions || !Array.isArray(permissions) || permissions.length === 0) {
201
+ return false;
202
+ }
203
+ return permissions.every((perm) => session.permissions.includes(perm));
204
+ }
205
+
193
206
  // src/core/roles.config.ts
194
207
  var PLATFORM_ROLES = [
195
208
  {
@@ -234,10 +247,14 @@ function buildSession(payload) {
234
247
  roles: normalizedRoles,
235
248
  permissions
236
249
  };
250
+ if (payload?.firstName) session.firstName = payload.firstName;
251
+ if (payload?.lastName) session.lastName = payload.lastName;
237
252
  if (payload?.projectId) session.projectId = payload.projectId;
238
253
  if (payload?.orgId) session.orgId = payload.orgId;
239
254
  if (payload?.org_id) session.org_id = payload.org_id;
240
255
  if (payload?.authType) session.authType = payload.authType;
256
+ if (payload?.createdAt) session.createdAt = payload.createdAt;
257
+ if (payload?.metadata) session.metadata = payload.metadata;
241
258
  Object.keys(payload || {}).forEach((key) => {
242
259
  if (![
243
260
  "sub",
@@ -291,7 +308,7 @@ var MetadataSchema = new import_mongoose2.default.Schema(
291
308
  );
292
309
  var OrgUserSchema = new import_mongoose2.default.Schema(
293
310
  {
294
- id: { type: String, default: (0, import_uuid.v4)(), index: true },
311
+ id: { type: String, default: (0, import_uuid.v4)(), index: true, unique: true },
295
312
  email: { type: String, required: true, unique: true },
296
313
  firstName: { type: String, required: true },
297
314
  lastName: { type: String, required: true },
@@ -411,10 +428,14 @@ function requireAuth() {
411
428
  const session = buildSession({
412
429
  sub: user.id.toString(),
413
430
  email: user.email,
431
+ firstName: user.firstName,
432
+ lastName: user.lastName,
433
+ metadata: user.metadata || [],
414
434
  roles: user.roles || [],
415
435
  orgId: user.orgId,
416
436
  org_id: user.orgId,
417
- projectId: user.projectId
437
+ projectId: user.projectId,
438
+ createdAt: user.createdAt
418
439
  });
419
440
  session.authType = "api-key";
420
441
  session.projectId = readProjectId(req) || user.projectId || void 0;
@@ -595,14 +616,14 @@ var AuthAdminService = class {
595
616
  async createUserInRealm(payload) {
596
617
  const hashedPassword = payload.credentials?.[0]?.value ? await import_bcrypt.default.hash(payload.credentials[0].value, 10) : void 0;
597
618
  const user = await OrgUser.create({
598
- username: payload.username,
619
+ id: crypto.randomUUID(),
599
620
  email: payload.email,
600
621
  firstName: payload.firstName,
601
622
  lastName: payload.lastName,
602
623
  projectId: payload.projectId,
603
624
  emailVerified: payload.emailVerified || false,
604
625
  passwordHash: hashedPassword,
605
- enabled: true
626
+ metadata: payload.metadata || []
606
627
  });
607
628
  return user;
608
629
  }
@@ -690,29 +711,13 @@ var EmailService = class {
690
711
  }
691
712
  };
692
713
 
693
- // src/utils/cookie.ts
694
- function cookieOpts(isRefresh = false) {
695
- const maxAge = isRefresh ? config.cookies.refreshTtlMs : config.cookies.accessTtlMs;
696
- const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
697
- return {
698
- httpOnly: true,
699
- secure,
700
- sameSite: "none",
701
- domain: process.env.COOKIE_DOMAIN,
702
- maxAge
703
- };
704
- }
705
- function clearOpts() {
706
- const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
707
- return {
708
- domain: process.env.COOKIE_DOMAIN,
709
- sameSite: "none",
710
- secure
711
- };
712
- }
713
-
714
714
  // src/express/auth.routes.ts
715
715
  function createAuthRouter(options = {}) {
716
+ const googleClientId = process.env.GOOGLE_CLIENT_ID;
717
+ const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
718
+ const googleRedirectUri = process.env.GOOGLE_REDIRECT_URI;
719
+ const googleDefaultRedirect = process.env.GOOGLE_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
720
+ const isGoogleEnabled = !!googleClientId && !!googleClientSecret && !!googleRedirectUri;
716
721
  if (options.config) {
717
722
  configureAuthX(options.config);
718
723
  }
@@ -737,8 +742,10 @@ function createAuthRouter(options = {}) {
737
742
  );
738
743
  r.post("/login", validateLogin, async (req, res) => {
739
744
  const { email: emailAddress, password } = req.body || {};
745
+ console.log(emailAddress, password, "body");
740
746
  try {
741
747
  const user = await OrgUser.findOne({ email: emailAddress }).select("+password").lean();
748
+ console.log(user, "user");
742
749
  if (!user) {
743
750
  return res.status(400).json({
744
751
  error: "Invalid email or password",
@@ -791,13 +798,13 @@ function createAuthRouter(options = {}) {
791
798
  firstName,
792
799
  lastName,
793
800
  projectId,
794
- credentials: [{ type: "password", value: password, temporary: false }]
801
+ credentials: [{ type: "password", value: password, temporary: false }],
802
+ metadata
795
803
  });
796
804
  await authAdmin.assignRealmRole(kcUser.id, "platform_user");
797
805
  const user = await OrgUser.findOneAndUpdate(
798
806
  { email: kcUser.email },
799
807
  {
800
- id: kcUser.id,
801
808
  email: kcUser.email,
802
809
  firstName,
803
810
  lastName,
@@ -837,8 +844,9 @@ function createAuthRouter(options = {}) {
837
844
  return res.json(req.user || null);
838
845
  });
839
846
  r.post("/logout", async (_req, res) => {
840
- res.clearCookie("access_token", clearOpts());
841
- res.clearCookie("refresh_token", clearOpts());
847
+ const clearOptions = buildClearCookieOptions(cookieConfig);
848
+ res.clearCookie("access_token", clearOptions);
849
+ res.clearCookie("refresh_token", clearOptions);
842
850
  res.json({ ok: true });
843
851
  });
844
852
  r.put("/:userId/metadata", requireAuth(), async (req, res) => {
@@ -1077,16 +1085,186 @@ function createAuthRouter(options = {}) {
1077
1085
  const user = await OrgUser.findOne({ email: req.query.email }).lean();
1078
1086
  res.json(user || null);
1079
1087
  });
1080
- r.get("/google", async (_req, res) => {
1081
- res.json({ url: "/auth/google/callback?code=demo" });
1088
+ r.get("/google", (req, res) => {
1089
+ if (!isGoogleEnabled) {
1090
+ return res.status(500).json({ error: "Google login not configured" });
1091
+ }
1092
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
1093
+ const params = new URLSearchParams({
1094
+ client_id: googleClientId,
1095
+ redirect_uri: googleRedirectUri,
1096
+ response_type: "code",
1097
+ scope: "openid email profile",
1098
+ access_type: "offline",
1099
+ prompt: "consent",
1100
+ state
1101
+ });
1102
+ const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
1103
+ res.redirect(url);
1082
1104
  });
1083
- r.get("/google/callback", async (_req, res) => {
1084
- res.cookie(
1085
- "access_token",
1086
- "ACCESS.TOKEN.PLACEHOLDER",
1087
- cookieOpts(false)
1088
- );
1089
- res.redirect("/");
1105
+ r.get("/google/callback", async (req, res) => {
1106
+ if (!isGoogleEnabled) {
1107
+ return res.status(500).json({ error: "Google login not configured" });
1108
+ }
1109
+ const code = String(req.query.code || "");
1110
+ const state = req.query.state ? String(req.query.state) : "";
1111
+ if (!code) {
1112
+ return res.status(400).json({ ok: false, error: "Missing authorization code" });
1113
+ }
1114
+ try {
1115
+ const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
1116
+ method: "POST",
1117
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1118
+ body: new URLSearchParams({
1119
+ code,
1120
+ client_id: googleClientId,
1121
+ client_secret: googleClientSecret,
1122
+ redirect_uri: googleRedirectUri,
1123
+ grant_type: "authorization_code"
1124
+ })
1125
+ });
1126
+ if (!tokenRes.ok) {
1127
+ const errJson = await tokenRes.json().catch(() => ({}));
1128
+ console.error("Google token error", errJson);
1129
+ return res.status(400).json({ ok: false, error: "Failed to exchange Google code" });
1130
+ }
1131
+ const tokenJson = await tokenRes.json();
1132
+ if (!tokenJson.id_token) {
1133
+ return res.status(400).json({ ok: false, error: "Missing id_token from Google" });
1134
+ }
1135
+ const decoded = import_jsonwebtoken4.default.decode(tokenJson.id_token);
1136
+ const email2 = decoded?.email;
1137
+ if (!email2) {
1138
+ return res.status(400).json({ ok: false, error: "Google account has no email" });
1139
+ }
1140
+ const emailVerified = decoded.email_verified ?? true;
1141
+ const firstName = decoded.given_name || "";
1142
+ const lastName = decoded.family_name || "";
1143
+ let user = await OrgUser.findOne({ email: email2 }).lean();
1144
+ if (!user) {
1145
+ const created = await OrgUser.create({
1146
+ email: email2,
1147
+ firstName,
1148
+ lastName,
1149
+ emailVerified,
1150
+ roles: ["platform_user"],
1151
+ projectId: null,
1152
+ metadata: []
1153
+ // you can also store googleId: decoded.sub
1154
+ });
1155
+ user = created.toObject();
1156
+ }
1157
+ const tokens = generateTokens(user);
1158
+ setAuthCookies(res, tokens, cookieConfig);
1159
+ const redirectTo = state ? decodeURIComponent(state) : googleDefaultRedirect;
1160
+ res.redirect(redirectTo);
1161
+ } catch (err) {
1162
+ console.error("Google callback error", err);
1163
+ const redirectError = googleDefaultRedirect.includes("?") ? `${googleDefaultRedirect}&error=google_login_failed` : `${googleDefaultRedirect}?error=google_login_failed`;
1164
+ res.redirect(redirectError);
1165
+ }
1166
+ });
1167
+ r.get("/github", (req, res) => {
1168
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1169
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1170
+ if (!githubClientId || !githubRedirectUri) {
1171
+ return res.status(500).json({ error: "GitHub login not configured" });
1172
+ }
1173
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
1174
+ const params = new URLSearchParams({
1175
+ client_id: githubClientId,
1176
+ redirect_uri: githubRedirectUri,
1177
+ scope: "user:email",
1178
+ state,
1179
+ allow_signup: "true"
1180
+ });
1181
+ const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
1182
+ res.redirect(url);
1183
+ });
1184
+ r.get("/github/callback", async (req, res) => {
1185
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1186
+ const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
1187
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1188
+ const githubDefaultRedirect = process.env.GITHUB_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
1189
+ if (!githubClientId || !githubClientSecret || !githubRedirectUri) {
1190
+ return res.status(500).json({ error: "GitHub login not configured" });
1191
+ }
1192
+ const code = String(req.query.code || "");
1193
+ const state = req.query.state ? String(req.query.state) : "";
1194
+ if (!code) {
1195
+ return res.status(400).json({ ok: false, error: "Missing GitHub code" });
1196
+ }
1197
+ try {
1198
+ const tokenRes = await fetch(
1199
+ "https://github.com/login/oauth/access_token",
1200
+ {
1201
+ method: "POST",
1202
+ headers: {
1203
+ Accept: "application/json",
1204
+ "Content-Type": "application/x-www-form-urlencoded"
1205
+ },
1206
+ body: new URLSearchParams({
1207
+ client_id: githubClientId,
1208
+ client_secret: githubClientSecret,
1209
+ code,
1210
+ redirect_uri: githubRedirectUri
1211
+ })
1212
+ }
1213
+ );
1214
+ const tokenJson = await tokenRes.json();
1215
+ if (!tokenJson.access_token) {
1216
+ console.error("GitHub token error:", tokenJson);
1217
+ return res.status(400).json({ ok: false, error: "Failed to get GitHub access token" });
1218
+ }
1219
+ const accessToken = tokenJson.access_token;
1220
+ const userRes = await fetch("https://api.github.com/user", {
1221
+ headers: {
1222
+ Authorization: `Bearer ${accessToken}`,
1223
+ Accept: "application/vnd.github+json"
1224
+ }
1225
+ });
1226
+ const githubUser = await userRes.json();
1227
+ const emailRes = await fetch("https://api.github.com/user/emails", {
1228
+ headers: {
1229
+ Authorization: `Bearer ${accessToken}`,
1230
+ Accept: "application/vnd.github+json"
1231
+ }
1232
+ });
1233
+ const emails = await emailRes.json();
1234
+ const primaryEmail = emails?.find(
1235
+ (e) => e.primary && e.verified
1236
+ )?.email;
1237
+ if (!primaryEmail) {
1238
+ return res.status(400).json({
1239
+ ok: false,
1240
+ error: "GitHub account has no verified email"
1241
+ });
1242
+ }
1243
+ const firstName = githubUser.name?.split(" ")[0] || "";
1244
+ const lastName = githubUser.name?.split(" ").slice(1).join(" ") || "";
1245
+ let user = await OrgUser.findOne({ email: primaryEmail }).lean();
1246
+ if (!user) {
1247
+ const created = await OrgUser.create({
1248
+ email: primaryEmail,
1249
+ firstName,
1250
+ lastName,
1251
+ emailVerified: true,
1252
+ roles: ["platform_user"],
1253
+ projectId: null,
1254
+ metadata: [],
1255
+ githubId: githubUser.id
1256
+ });
1257
+ user = created.toObject();
1258
+ }
1259
+ const tokens = generateTokens(user);
1260
+ setAuthCookies(res, tokens, cookieConfig);
1261
+ const redirectTo = state ? decodeURIComponent(state) : githubDefaultRedirect;
1262
+ res.redirect(redirectTo);
1263
+ } catch (err) {
1264
+ console.error("GitHub callback error:", err);
1265
+ const redirectError = githubDefaultRedirect.includes("?") ? `${githubDefaultRedirect}&error=github_login_failed` : `${githubDefaultRedirect}?error=github_login_failed`;
1266
+ res.redirect(redirectError);
1267
+ }
1090
1268
  });
1091
1269
  r.get("/get-users", async (req, res) => {
1092
1270
  const user = await OrgUser.find({ projectId: req.query.projectId }).lean();
@@ -1164,6 +1342,11 @@ function generateTokens(user) {
1164
1342
  orgId: user.orgId || null,
1165
1343
  org_id: user.orgId || null,
1166
1344
  projectId: user.projectId || null,
1345
+ firstName: user.firstName,
1346
+ lastName: user.lastName,
1347
+ emailVerified: user.emailVerified,
1348
+ createdAt: user.createdAt,
1349
+ metadata: user.metadata,
1167
1350
  type: "user"
1168
1351
  };
1169
1352
  const accessToken = import_jsonwebtoken4.default.sign(accessPayload, process.env.JWT_SECRET, {