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.
@@ -6,24 +6,6 @@ import express, {
6
6
  } from "express";
7
7
  import jwt4 from "jsonwebtoken";
8
8
 
9
- // src/core/utils.ts
10
- function baseProjectCookieOptionsFrom(cookie) {
11
- const base = {
12
- secure: cookie.secure ?? false,
13
- sameSite: cookie.sameSite ?? "lax",
14
- path: cookie.path ?? "/",
15
- maxAge: cookie.maxAgeMs
16
- };
17
- if (cookie.domain) base.domain = cookie.domain;
18
- return base;
19
- }
20
- function hasAnyRole(session, roles) {
21
- if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
22
- return false;
23
- }
24
- return roles.some((role) => session.roles.includes(role));
25
- }
26
-
27
9
  // src/config/loadConfig.ts
28
10
  function loadConfig() {
29
11
  return {
@@ -87,6 +69,37 @@ function isPlainObject(value) {
87
69
  return typeof value === "object" && value !== null && !Array.isArray(value);
88
70
  }
89
71
 
72
+ // src/core/utils.ts
73
+ function baseProjectCookieOptionsFrom(cookie) {
74
+ const base = {
75
+ secure: cookie.secure ?? false,
76
+ sameSite: cookie.sameSite ?? "lax",
77
+ path: cookie.path ?? "/",
78
+ maxAge: cookie.maxAgeMs
79
+ };
80
+ if (cookie.domain) base.domain = cookie.domain;
81
+ return base;
82
+ }
83
+ function buildClearCookieOptions(cookie) {
84
+ const opts = {
85
+ httpOnly: true,
86
+ // not strictly required but fine
87
+ secure: cookie.secure ?? false,
88
+ sameSite: cookie.sameSite ?? "lax",
89
+ path: cookie.path ?? "/"
90
+ };
91
+ if (cookie.domain) {
92
+ opts.domain = cookie.domain;
93
+ }
94
+ return opts;
95
+ }
96
+ function hasAnyRole(session, roles) {
97
+ if (!session || !session.roles || !Array.isArray(roles) || roles.length === 0) {
98
+ return false;
99
+ }
100
+ return roles.some((role) => session.roles.includes(role));
101
+ }
102
+
90
103
  // src/core/roles.config.ts
91
104
  var PLATFORM_ROLES = [
92
105
  {
@@ -131,10 +144,14 @@ function buildSession(payload) {
131
144
  roles: normalizedRoles,
132
145
  permissions
133
146
  };
147
+ if (payload?.firstName) session.firstName = payload.firstName;
148
+ if (payload?.lastName) session.lastName = payload.lastName;
134
149
  if (payload?.projectId) session.projectId = payload.projectId;
135
150
  if (payload?.orgId) session.orgId = payload.orgId;
136
151
  if (payload?.org_id) session.org_id = payload.org_id;
137
152
  if (payload?.authType) session.authType = payload.authType;
153
+ if (payload?.createdAt) session.createdAt = payload.createdAt;
154
+ if (payload?.metadata) session.metadata = payload.metadata;
138
155
  Object.keys(payload || {}).forEach((key) => {
139
156
  if (![
140
157
  "sub",
@@ -188,7 +205,7 @@ var MetadataSchema = new mongoose2.Schema(
188
205
  );
189
206
  var OrgUserSchema = new mongoose2.Schema(
190
207
  {
191
- id: { type: String, default: uuid(), index: true },
208
+ id: { type: String, default: uuid(), index: true, unique: true },
192
209
  email: { type: String, required: true, unique: true },
193
210
  firstName: { type: String, required: true },
194
211
  lastName: { type: String, required: true },
@@ -308,10 +325,14 @@ function requireAuth() {
308
325
  const session = buildSession({
309
326
  sub: user.id.toString(),
310
327
  email: user.email,
328
+ firstName: user.firstName,
329
+ lastName: user.lastName,
330
+ metadata: user.metadata || [],
311
331
  roles: user.roles || [],
312
332
  orgId: user.orgId,
313
333
  org_id: user.orgId,
314
- projectId: user.projectId
334
+ projectId: user.projectId,
335
+ createdAt: user.createdAt
315
336
  });
316
337
  session.authType = "api-key";
317
338
  session.projectId = readProjectId(req) || user.projectId || void 0;
@@ -477,14 +498,14 @@ var AuthAdminService = class {
477
498
  async createUserInRealm(payload) {
478
499
  const hashedPassword = payload.credentials?.[0]?.value ? await bcrypt.hash(payload.credentials[0].value, 10) : void 0;
479
500
  const user = await OrgUser.create({
480
- username: payload.username,
501
+ id: crypto.randomUUID(),
481
502
  email: payload.email,
482
503
  firstName: payload.firstName,
483
504
  lastName: payload.lastName,
484
505
  projectId: payload.projectId,
485
506
  emailVerified: payload.emailVerified || false,
486
507
  passwordHash: hashedPassword,
487
- enabled: true
508
+ metadata: payload.metadata || []
488
509
  });
489
510
  return user;
490
511
  }
@@ -572,29 +593,13 @@ var EmailService = class {
572
593
  }
573
594
  };
574
595
 
575
- // src/utils/cookie.ts
576
- function cookieOpts(isRefresh = false) {
577
- const maxAge = isRefresh ? config.cookies.refreshTtlMs : config.cookies.accessTtlMs;
578
- const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
579
- return {
580
- httpOnly: true,
581
- secure,
582
- sameSite: "none",
583
- domain: process.env.COOKIE_DOMAIN,
584
- maxAge
585
- };
586
- }
587
- function clearOpts() {
588
- const secure = process.env.NODE_ENV === "production" ? process.env.COOKIE_SECURE ?? true : false;
589
- return {
590
- domain: process.env.COOKIE_DOMAIN,
591
- sameSite: "none",
592
- secure
593
- };
594
- }
595
-
596
596
  // src/express/auth.routes.ts
597
597
  function createAuthRouter(options = {}) {
598
+ const googleClientId = process.env.GOOGLE_CLIENT_ID;
599
+ const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
600
+ const googleRedirectUri = process.env.GOOGLE_REDIRECT_URI;
601
+ const googleDefaultRedirect = process.env.GOOGLE_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
602
+ const isGoogleEnabled = !!googleClientId && !!googleClientSecret && !!googleRedirectUri;
598
603
  if (options.config) {
599
604
  configureAuthX(options.config);
600
605
  }
@@ -619,8 +624,10 @@ function createAuthRouter(options = {}) {
619
624
  );
620
625
  r.post("/login", validateLogin, async (req, res) => {
621
626
  const { email: emailAddress, password } = req.body || {};
627
+ console.log(emailAddress, password, "body");
622
628
  try {
623
629
  const user = await OrgUser.findOne({ email: emailAddress }).select("+password").lean();
630
+ console.log(user, "user");
624
631
  if (!user) {
625
632
  return res.status(400).json({
626
633
  error: "Invalid email or password",
@@ -673,13 +680,13 @@ function createAuthRouter(options = {}) {
673
680
  firstName,
674
681
  lastName,
675
682
  projectId,
676
- credentials: [{ type: "password", value: password, temporary: false }]
683
+ credentials: [{ type: "password", value: password, temporary: false }],
684
+ metadata
677
685
  });
678
686
  await authAdmin.assignRealmRole(kcUser.id, "platform_user");
679
687
  const user = await OrgUser.findOneAndUpdate(
680
688
  { email: kcUser.email },
681
689
  {
682
- id: kcUser.id,
683
690
  email: kcUser.email,
684
691
  firstName,
685
692
  lastName,
@@ -719,8 +726,9 @@ function createAuthRouter(options = {}) {
719
726
  return res.json(req.user || null);
720
727
  });
721
728
  r.post("/logout", async (_req, res) => {
722
- res.clearCookie("access_token", clearOpts());
723
- res.clearCookie("refresh_token", clearOpts());
729
+ const clearOptions = buildClearCookieOptions(cookieConfig);
730
+ res.clearCookie("access_token", clearOptions);
731
+ res.clearCookie("refresh_token", clearOptions);
724
732
  res.json({ ok: true });
725
733
  });
726
734
  r.put("/:userId/metadata", requireAuth(), async (req, res) => {
@@ -959,16 +967,186 @@ function createAuthRouter(options = {}) {
959
967
  const user = await OrgUser.findOne({ email: req.query.email }).lean();
960
968
  res.json(user || null);
961
969
  });
962
- r.get("/google", async (_req, res) => {
963
- res.json({ url: "/auth/google/callback?code=demo" });
970
+ r.get("/google", (req, res) => {
971
+ if (!isGoogleEnabled) {
972
+ return res.status(500).json({ error: "Google login not configured" });
973
+ }
974
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
975
+ const params = new URLSearchParams({
976
+ client_id: googleClientId,
977
+ redirect_uri: googleRedirectUri,
978
+ response_type: "code",
979
+ scope: "openid email profile",
980
+ access_type: "offline",
981
+ prompt: "consent",
982
+ state
983
+ });
984
+ const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
985
+ res.redirect(url);
964
986
  });
965
- r.get("/google/callback", async (_req, res) => {
966
- res.cookie(
967
- "access_token",
968
- "ACCESS.TOKEN.PLACEHOLDER",
969
- cookieOpts(false)
970
- );
971
- res.redirect("/");
987
+ r.get("/google/callback", async (req, res) => {
988
+ if (!isGoogleEnabled) {
989
+ return res.status(500).json({ error: "Google login not configured" });
990
+ }
991
+ const code = String(req.query.code || "");
992
+ const state = req.query.state ? String(req.query.state) : "";
993
+ if (!code) {
994
+ return res.status(400).json({ ok: false, error: "Missing authorization code" });
995
+ }
996
+ try {
997
+ const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
998
+ method: "POST",
999
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
1000
+ body: new URLSearchParams({
1001
+ code,
1002
+ client_id: googleClientId,
1003
+ client_secret: googleClientSecret,
1004
+ redirect_uri: googleRedirectUri,
1005
+ grant_type: "authorization_code"
1006
+ })
1007
+ });
1008
+ if (!tokenRes.ok) {
1009
+ const errJson = await tokenRes.json().catch(() => ({}));
1010
+ console.error("Google token error", errJson);
1011
+ return res.status(400).json({ ok: false, error: "Failed to exchange Google code" });
1012
+ }
1013
+ const tokenJson = await tokenRes.json();
1014
+ if (!tokenJson.id_token) {
1015
+ return res.status(400).json({ ok: false, error: "Missing id_token from Google" });
1016
+ }
1017
+ const decoded = jwt4.decode(tokenJson.id_token);
1018
+ const email2 = decoded?.email;
1019
+ if (!email2) {
1020
+ return res.status(400).json({ ok: false, error: "Google account has no email" });
1021
+ }
1022
+ const emailVerified = decoded.email_verified ?? true;
1023
+ const firstName = decoded.given_name || "";
1024
+ const lastName = decoded.family_name || "";
1025
+ let user = await OrgUser.findOne({ email: email2 }).lean();
1026
+ if (!user) {
1027
+ const created = await OrgUser.create({
1028
+ email: email2,
1029
+ firstName,
1030
+ lastName,
1031
+ emailVerified,
1032
+ roles: ["platform_user"],
1033
+ projectId: null,
1034
+ metadata: []
1035
+ // you can also store googleId: decoded.sub
1036
+ });
1037
+ user = created.toObject();
1038
+ }
1039
+ const tokens = generateTokens(user);
1040
+ setAuthCookies(res, tokens, cookieConfig);
1041
+ const redirectTo = state ? decodeURIComponent(state) : googleDefaultRedirect;
1042
+ res.redirect(redirectTo);
1043
+ } catch (err) {
1044
+ console.error("Google callback error", err);
1045
+ const redirectError = googleDefaultRedirect.includes("?") ? `${googleDefaultRedirect}&error=google_login_failed` : `${googleDefaultRedirect}?error=google_login_failed`;
1046
+ res.redirect(redirectError);
1047
+ }
1048
+ });
1049
+ r.get("/github", (req, res) => {
1050
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1051
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1052
+ if (!githubClientId || !githubRedirectUri) {
1053
+ return res.status(500).json({ error: "GitHub login not configured" });
1054
+ }
1055
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
1056
+ const params = new URLSearchParams({
1057
+ client_id: githubClientId,
1058
+ redirect_uri: githubRedirectUri,
1059
+ scope: "user:email",
1060
+ state,
1061
+ allow_signup: "true"
1062
+ });
1063
+ const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
1064
+ res.redirect(url);
1065
+ });
1066
+ r.get("/github/callback", async (req, res) => {
1067
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1068
+ const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
1069
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1070
+ const githubDefaultRedirect = process.env.GITHUB_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
1071
+ if (!githubClientId || !githubClientSecret || !githubRedirectUri) {
1072
+ return res.status(500).json({ error: "GitHub login not configured" });
1073
+ }
1074
+ const code = String(req.query.code || "");
1075
+ const state = req.query.state ? String(req.query.state) : "";
1076
+ if (!code) {
1077
+ return res.status(400).json({ ok: false, error: "Missing GitHub code" });
1078
+ }
1079
+ try {
1080
+ const tokenRes = await fetch(
1081
+ "https://github.com/login/oauth/access_token",
1082
+ {
1083
+ method: "POST",
1084
+ headers: {
1085
+ Accept: "application/json",
1086
+ "Content-Type": "application/x-www-form-urlencoded"
1087
+ },
1088
+ body: new URLSearchParams({
1089
+ client_id: githubClientId,
1090
+ client_secret: githubClientSecret,
1091
+ code,
1092
+ redirect_uri: githubRedirectUri
1093
+ })
1094
+ }
1095
+ );
1096
+ const tokenJson = await tokenRes.json();
1097
+ if (!tokenJson.access_token) {
1098
+ console.error("GitHub token error:", tokenJson);
1099
+ return res.status(400).json({ ok: false, error: "Failed to get GitHub access token" });
1100
+ }
1101
+ const accessToken = tokenJson.access_token;
1102
+ const userRes = await fetch("https://api.github.com/user", {
1103
+ headers: {
1104
+ Authorization: `Bearer ${accessToken}`,
1105
+ Accept: "application/vnd.github+json"
1106
+ }
1107
+ });
1108
+ const githubUser = await userRes.json();
1109
+ const emailRes = await fetch("https://api.github.com/user/emails", {
1110
+ headers: {
1111
+ Authorization: `Bearer ${accessToken}`,
1112
+ Accept: "application/vnd.github+json"
1113
+ }
1114
+ });
1115
+ const emails = await emailRes.json();
1116
+ const primaryEmail = emails?.find(
1117
+ (e) => e.primary && e.verified
1118
+ )?.email;
1119
+ if (!primaryEmail) {
1120
+ return res.status(400).json({
1121
+ ok: false,
1122
+ error: "GitHub account has no verified email"
1123
+ });
1124
+ }
1125
+ const firstName = githubUser.name?.split(" ")[0] || "";
1126
+ const lastName = githubUser.name?.split(" ").slice(1).join(" ") || "";
1127
+ let user = await OrgUser.findOne({ email: primaryEmail }).lean();
1128
+ if (!user) {
1129
+ const created = await OrgUser.create({
1130
+ email: primaryEmail,
1131
+ firstName,
1132
+ lastName,
1133
+ emailVerified: true,
1134
+ roles: ["platform_user"],
1135
+ projectId: null,
1136
+ metadata: [],
1137
+ githubId: githubUser.id
1138
+ });
1139
+ user = created.toObject();
1140
+ }
1141
+ const tokens = generateTokens(user);
1142
+ setAuthCookies(res, tokens, cookieConfig);
1143
+ const redirectTo = state ? decodeURIComponent(state) : githubDefaultRedirect;
1144
+ res.redirect(redirectTo);
1145
+ } catch (err) {
1146
+ console.error("GitHub callback error:", err);
1147
+ const redirectError = githubDefaultRedirect.includes("?") ? `${githubDefaultRedirect}&error=github_login_failed` : `${githubDefaultRedirect}?error=github_login_failed`;
1148
+ res.redirect(redirectError);
1149
+ }
972
1150
  });
973
1151
  r.get("/get-users", async (req, res) => {
974
1152
  const user = await OrgUser.find({ projectId: req.query.projectId }).lean();
@@ -1046,6 +1224,11 @@ function generateTokens(user) {
1046
1224
  orgId: user.orgId || null,
1047
1225
  org_id: user.orgId || null,
1048
1226
  projectId: user.projectId || null,
1227
+ firstName: user.firstName,
1228
+ lastName: user.lastName,
1229
+ emailVerified: user.emailVerified,
1230
+ createdAt: user.createdAt,
1231
+ metadata: user.metadata,
1049
1232
  type: "user"
1050
1233
  };
1051
1234
  const accessToken = jwt4.sign(accessPayload, process.env.JWT_SECRET, {