aaspai-authx 0.1.0 → 0.1.1

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
  {
@@ -226,7 +239,7 @@ var MetadataSchema = new import_mongoose2.default.Schema(
226
239
  );
227
240
  var OrgUserSchema = new import_mongoose2.default.Schema(
228
241
  {
229
- id: { type: String, default: (0, import_uuid.v4)(), index: true },
242
+ id: { type: String, default: (0, import_uuid.v4)(), index: true, unique: true },
230
243
  email: { type: String, required: true, unique: true },
231
244
  firstName: { type: String, required: true },
232
245
  lastName: { type: String, required: true },
@@ -446,6 +459,7 @@ var Invite = import_mongoose3.default.model("Invite", InviteSchema);
446
459
  // src/services/auth-admin.service.ts
447
460
  var import_bcrypt = __toESM(require("bcrypt"), 1);
448
461
  var import_jsonwebtoken2 = __toESM(require("jsonwebtoken"), 1);
462
+ var import_uuid2 = require("uuid");
449
463
 
450
464
  // src/models/client.model.ts
451
465
  var import_mongoose4 = __toESM(require("mongoose"), 1);
@@ -515,14 +529,14 @@ var AuthAdminService = class {
515
529
  async createUserInRealm(payload) {
516
530
  const hashedPassword = payload.credentials?.[0]?.value ? await import_bcrypt.default.hash(payload.credentials[0].value, 10) : void 0;
517
531
  const user = await OrgUser.create({
518
- username: payload.username,
532
+ id: (0, import_uuid2.v4)(),
519
533
  email: payload.email,
520
534
  firstName: payload.firstName,
521
535
  lastName: payload.lastName,
522
536
  projectId: payload.projectId,
523
537
  emailVerified: payload.emailVerified || false,
524
538
  passwordHash: hashedPassword,
525
- enabled: true
539
+ metadata: payload.metadata || []
526
540
  });
527
541
  return user;
528
542
  }
@@ -610,29 +624,13 @@ var EmailService = class {
610
624
  }
611
625
  };
612
626
 
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
627
  // src/express/auth.routes.ts
635
628
  function createAuthRouter(options = {}) {
629
+ const googleClientId = process.env.GOOGLE_CLIENT_ID;
630
+ const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
631
+ const googleRedirectUri = process.env.GOOGLE_REDIRECT_URI;
632
+ const googleDefaultRedirect = process.env.GOOGLE_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
633
+ const isGoogleEnabled = !!googleClientId && !!googleClientSecret && !!googleRedirectUri;
636
634
  if (options.config) {
637
635
  configureAuthX(options.config);
638
636
  }
@@ -657,8 +655,10 @@ function createAuthRouter(options = {}) {
657
655
  );
658
656
  r.post("/login", validateLogin, async (req, res) => {
659
657
  const { email: emailAddress, password } = req.body || {};
658
+ console.log(emailAddress, password, "body");
660
659
  try {
661
660
  const user = await OrgUser.findOne({ email: emailAddress }).select("+password").lean();
661
+ console.log(user, "user");
662
662
  if (!user) {
663
663
  return res.status(400).json({
664
664
  error: "Invalid email or password",
@@ -711,13 +711,13 @@ function createAuthRouter(options = {}) {
711
711
  firstName,
712
712
  lastName,
713
713
  projectId,
714
- credentials: [{ type: "password", value: password, temporary: false }]
714
+ credentials: [{ type: "password", value: password, temporary: false }],
715
+ metadata
715
716
  });
716
717
  await authAdmin.assignRealmRole(kcUser.id, "platform_user");
717
718
  const user = await OrgUser.findOneAndUpdate(
718
719
  { email: kcUser.email },
719
720
  {
720
- id: kcUser.id,
721
721
  email: kcUser.email,
722
722
  firstName,
723
723
  lastName,
@@ -757,8 +757,9 @@ function createAuthRouter(options = {}) {
757
757
  return res.json(req.user || null);
758
758
  });
759
759
  r.post("/logout", async (_req, res) => {
760
- res.clearCookie("access_token", clearOpts());
761
- res.clearCookie("refresh_token", clearOpts());
760
+ const clearOptions = buildClearCookieOptions(cookieConfig);
761
+ res.clearCookie("access_token", clearOptions);
762
+ res.clearCookie("refresh_token", clearOptions);
762
763
  res.json({ ok: true });
763
764
  });
764
765
  r.put("/:userId/metadata", requireAuth(), async (req, res) => {
@@ -997,16 +998,186 @@ function createAuthRouter(options = {}) {
997
998
  const user = await OrgUser.findOne({ email: req.query.email }).lean();
998
999
  res.json(user || null);
999
1000
  });
1000
- r.get("/google", async (_req, res) => {
1001
- res.json({ url: "/auth/google/callback?code=demo" });
1001
+ r.get("/google", (req, res) => {
1002
+ if (!isGoogleEnabled) {
1003
+ return res.status(500).json({ error: "Google login not configured" });
1004
+ }
1005
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
1006
+ const params = new URLSearchParams({
1007
+ client_id: googleClientId,
1008
+ redirect_uri: googleRedirectUri,
1009
+ response_type: "code",
1010
+ scope: "openid email profile",
1011
+ access_type: "offline",
1012
+ prompt: "consent",
1013
+ state
1014
+ });
1015
+ const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
1016
+ res.redirect(url);
1002
1017
  });
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("/");
1018
+ r.get("/google/callback", async (req, res) => {
1019
+ if (!isGoogleEnabled) {
1020
+ return res.status(500).json({ error: "Google login not configured" });
1021
+ }
1022
+ const code = String(req.query.code || "");
1023
+ const state = req.query.state ? String(req.query.state) : "";
1024
+ if (!code) {
1025
+ return res.status(400).json({ ok: false, error: "Missing authorization code" });
1026
+ }
1027
+ try {
1028
+ const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
1029
+ method: "POST",
1030
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1031
+ body: new URLSearchParams({
1032
+ code,
1033
+ client_id: googleClientId,
1034
+ client_secret: googleClientSecret,
1035
+ redirect_uri: googleRedirectUri,
1036
+ grant_type: "authorization_code"
1037
+ })
1038
+ });
1039
+ if (!tokenRes.ok) {
1040
+ const errJson = await tokenRes.json().catch(() => ({}));
1041
+ console.error("Google token error", errJson);
1042
+ return res.status(400).json({ ok: false, error: "Failed to exchange Google code" });
1043
+ }
1044
+ const tokenJson = await tokenRes.json();
1045
+ if (!tokenJson.id_token) {
1046
+ return res.status(400).json({ ok: false, error: "Missing id_token from Google" });
1047
+ }
1048
+ const decoded = import_jsonwebtoken4.default.decode(tokenJson.id_token);
1049
+ const email2 = decoded?.email;
1050
+ if (!email2) {
1051
+ return res.status(400).json({ ok: false, error: "Google account has no email" });
1052
+ }
1053
+ const emailVerified = decoded.email_verified ?? true;
1054
+ const firstName = decoded.given_name || "";
1055
+ const lastName = decoded.family_name || "";
1056
+ let user = await OrgUser.findOne({ email: email2 }).lean();
1057
+ if (!user) {
1058
+ const created = await OrgUser.create({
1059
+ email: email2,
1060
+ firstName,
1061
+ lastName,
1062
+ emailVerified,
1063
+ roles: ["platform_user"],
1064
+ projectId: null,
1065
+ metadata: []
1066
+ // you can also store googleId: decoded.sub
1067
+ });
1068
+ user = created.toObject();
1069
+ }
1070
+ const tokens = generateTokens(user);
1071
+ setAuthCookies(res, tokens, cookieConfig);
1072
+ const redirectTo = state ? decodeURIComponent(state) : googleDefaultRedirect;
1073
+ res.redirect(redirectTo);
1074
+ } catch (err) {
1075
+ console.error("Google callback error", err);
1076
+ const redirectError = googleDefaultRedirect.includes("?") ? `${googleDefaultRedirect}&error=google_login_failed` : `${googleDefaultRedirect}?error=google_login_failed`;
1077
+ res.redirect(redirectError);
1078
+ }
1079
+ });
1080
+ r.get("/github", (req, res) => {
1081
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1082
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1083
+ if (!githubClientId || !githubRedirectUri) {
1084
+ return res.status(500).json({ error: "GitHub login not configured" });
1085
+ }
1086
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
1087
+ const params = new URLSearchParams({
1088
+ client_id: githubClientId,
1089
+ redirect_uri: githubRedirectUri,
1090
+ scope: "user:email",
1091
+ state,
1092
+ allow_signup: "true"
1093
+ });
1094
+ const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
1095
+ res.redirect(url);
1096
+ });
1097
+ r.get("/github/callback", async (req, res) => {
1098
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1099
+ const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
1100
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1101
+ const githubDefaultRedirect = process.env.GITHUB_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
1102
+ if (!githubClientId || !githubClientSecret || !githubRedirectUri) {
1103
+ return res.status(500).json({ error: "GitHub login not configured" });
1104
+ }
1105
+ const code = String(req.query.code || "");
1106
+ const state = req.query.state ? String(req.query.state) : "";
1107
+ if (!code) {
1108
+ return res.status(400).json({ ok: false, error: "Missing GitHub code" });
1109
+ }
1110
+ try {
1111
+ const tokenRes = await fetch(
1112
+ "https://github.com/login/oauth/access_token",
1113
+ {
1114
+ method: "POST",
1115
+ headers: {
1116
+ Accept: "application/json",
1117
+ "Content-Type": "application/x-www-form-urlencoded"
1118
+ },
1119
+ body: new URLSearchParams({
1120
+ client_id: githubClientId,
1121
+ client_secret: githubClientSecret,
1122
+ code,
1123
+ redirect_uri: githubRedirectUri
1124
+ })
1125
+ }
1126
+ );
1127
+ const tokenJson = await tokenRes.json();
1128
+ if (!tokenJson.access_token) {
1129
+ console.error("GitHub token error:", tokenJson);
1130
+ return res.status(400).json({ ok: false, error: "Failed to get GitHub access token" });
1131
+ }
1132
+ const accessToken = tokenJson.access_token;
1133
+ const userRes = await fetch("https://api.github.com/user", {
1134
+ headers: {
1135
+ Authorization: `Bearer ${accessToken}`,
1136
+ Accept: "application/vnd.github+json"
1137
+ }
1138
+ });
1139
+ const githubUser = await userRes.json();
1140
+ const emailRes = await fetch("https://api.github.com/user/emails", {
1141
+ headers: {
1142
+ Authorization: `Bearer ${accessToken}`,
1143
+ Accept: "application/vnd.github+json"
1144
+ }
1145
+ });
1146
+ const emails = await emailRes.json();
1147
+ const primaryEmail = emails?.find(
1148
+ (e) => e.primary && e.verified
1149
+ )?.email;
1150
+ if (!primaryEmail) {
1151
+ return res.status(400).json({
1152
+ ok: false,
1153
+ error: "GitHub account has no verified email"
1154
+ });
1155
+ }
1156
+ const firstName = githubUser.name?.split(" ")[0] || "";
1157
+ const lastName = githubUser.name?.split(" ").slice(1).join(" ") || "";
1158
+ let user = await OrgUser.findOne({ email: primaryEmail }).lean();
1159
+ if (!user) {
1160
+ const created = await OrgUser.create({
1161
+ email: primaryEmail,
1162
+ firstName,
1163
+ lastName,
1164
+ emailVerified: true,
1165
+ roles: ["platform_user"],
1166
+ projectId: null,
1167
+ metadata: [],
1168
+ githubId: githubUser.id
1169
+ });
1170
+ user = created.toObject();
1171
+ }
1172
+ const tokens = generateTokens(user);
1173
+ setAuthCookies(res, tokens, cookieConfig);
1174
+ const redirectTo = state ? decodeURIComponent(state) : githubDefaultRedirect;
1175
+ res.redirect(redirectTo);
1176
+ } catch (err) {
1177
+ console.error("GitHub callback error:", err);
1178
+ const redirectError = githubDefaultRedirect.includes("?") ? `${githubDefaultRedirect}&error=github_login_failed` : `${githubDefaultRedirect}?error=github_login_failed`;
1179
+ res.redirect(redirectError);
1180
+ }
1010
1181
  });
1011
1182
  r.get("/get-users", async (req, res) => {
1012
1183
  const user = await OrgUser.find({ projectId: req.query.projectId }).lean();