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.
@@ -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
- username: payload.username,
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
- enabled: true
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
- res.clearCookie("access_token", clearOpts());
761
- res.clearCookie("refresh_token", clearOpts());
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", async (_req, res) => {
1001
- res.json({ url: "/auth/google/callback?code=demo" });
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 (_req, res) => {
1004
- res.cookie(
1005
- "access_token",
1006
- "ACCESS.TOKEN.PLACEHOLDER",
1007
- cookieOpts(false)
1008
- );
1009
- res.redirect("/");
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, {