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