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