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.
@@ -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
  {
@@ -188,7 +201,7 @@ var MetadataSchema = new mongoose2.Schema(
188
201
  );
189
202
  var OrgUserSchema = new mongoose2.Schema(
190
203
  {
191
- id: { type: String, default: uuid(), index: true },
204
+ id: { type: String, default: uuid(), index: true, unique: true },
192
205
  email: { type: String, required: true, unique: true },
193
206
  firstName: { type: String, required: true },
194
207
  lastName: { type: String, required: true },
@@ -408,6 +421,7 @@ var Invite = mongoose3.model("Invite", InviteSchema);
408
421
  // src/services/auth-admin.service.ts
409
422
  import bcrypt from "bcrypt";
410
423
  import jwt2 from "jsonwebtoken";
424
+ import { v4 as uuid2 } from "uuid";
411
425
 
412
426
  // src/models/client.model.ts
413
427
  import mongoose4, { Schema as Schema2 } from "mongoose";
@@ -477,14 +491,14 @@ var AuthAdminService = class {
477
491
  async createUserInRealm(payload) {
478
492
  const hashedPassword = payload.credentials?.[0]?.value ? await bcrypt.hash(payload.credentials[0].value, 10) : void 0;
479
493
  const user = await OrgUser.create({
480
- username: payload.username,
494
+ id: uuid2(),
481
495
  email: payload.email,
482
496
  firstName: payload.firstName,
483
497
  lastName: payload.lastName,
484
498
  projectId: payload.projectId,
485
499
  emailVerified: payload.emailVerified || false,
486
500
  passwordHash: hashedPassword,
487
- enabled: true
501
+ metadata: payload.metadata || []
488
502
  });
489
503
  return user;
490
504
  }
@@ -572,29 +586,13 @@ var EmailService = class {
572
586
  }
573
587
  };
574
588
 
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
589
  // src/express/auth.routes.ts
597
590
  function createAuthRouter(options = {}) {
591
+ const googleClientId = process.env.GOOGLE_CLIENT_ID;
592
+ const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
593
+ const googleRedirectUri = process.env.GOOGLE_REDIRECT_URI;
594
+ const googleDefaultRedirect = process.env.GOOGLE_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
595
+ const isGoogleEnabled = !!googleClientId && !!googleClientSecret && !!googleRedirectUri;
598
596
  if (options.config) {
599
597
  configureAuthX(options.config);
600
598
  }
@@ -619,8 +617,10 @@ function createAuthRouter(options = {}) {
619
617
  );
620
618
  r.post("/login", validateLogin, async (req, res) => {
621
619
  const { email: emailAddress, password } = req.body || {};
620
+ console.log(emailAddress, password, "body");
622
621
  try {
623
622
  const user = await OrgUser.findOne({ email: emailAddress }).select("+password").lean();
623
+ console.log(user, "user");
624
624
  if (!user) {
625
625
  return res.status(400).json({
626
626
  error: "Invalid email or password",
@@ -673,13 +673,13 @@ function createAuthRouter(options = {}) {
673
673
  firstName,
674
674
  lastName,
675
675
  projectId,
676
- credentials: [{ type: "password", value: password, temporary: false }]
676
+ credentials: [{ type: "password", value: password, temporary: false }],
677
+ metadata
677
678
  });
678
679
  await authAdmin.assignRealmRole(kcUser.id, "platform_user");
679
680
  const user = await OrgUser.findOneAndUpdate(
680
681
  { email: kcUser.email },
681
682
  {
682
- id: kcUser.id,
683
683
  email: kcUser.email,
684
684
  firstName,
685
685
  lastName,
@@ -719,8 +719,9 @@ function createAuthRouter(options = {}) {
719
719
  return res.json(req.user || null);
720
720
  });
721
721
  r.post("/logout", async (_req, res) => {
722
- res.clearCookie("access_token", clearOpts());
723
- res.clearCookie("refresh_token", clearOpts());
722
+ const clearOptions = buildClearCookieOptions(cookieConfig);
723
+ res.clearCookie("access_token", clearOptions);
724
+ res.clearCookie("refresh_token", clearOptions);
724
725
  res.json({ ok: true });
725
726
  });
726
727
  r.put("/:userId/metadata", requireAuth(), async (req, res) => {
@@ -959,16 +960,186 @@ function createAuthRouter(options = {}) {
959
960
  const user = await OrgUser.findOne({ email: req.query.email }).lean();
960
961
  res.json(user || null);
961
962
  });
962
- r.get("/google", async (_req, res) => {
963
- res.json({ url: "/auth/google/callback?code=demo" });
963
+ r.get("/google", (req, res) => {
964
+ if (!isGoogleEnabled) {
965
+ return res.status(500).json({ error: "Google login not configured" });
966
+ }
967
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
968
+ const params = new URLSearchParams({
969
+ client_id: googleClientId,
970
+ redirect_uri: googleRedirectUri,
971
+ response_type: "code",
972
+ scope: "openid email profile",
973
+ access_type: "offline",
974
+ prompt: "consent",
975
+ state
976
+ });
977
+ const url = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
978
+ res.redirect(url);
964
979
  });
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("/");
980
+ r.get("/google/callback", async (req, res) => {
981
+ if (!isGoogleEnabled) {
982
+ return res.status(500).json({ error: "Google login not configured" });
983
+ }
984
+ const code = String(req.query.code || "");
985
+ const state = req.query.state ? String(req.query.state) : "";
986
+ if (!code) {
987
+ return res.status(400).json({ ok: false, error: "Missing authorization code" });
988
+ }
989
+ try {
990
+ const tokenRes = await fetch("https://oauth2.googleapis.com/token", {
991
+ method: "POST",
992
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
993
+ body: new URLSearchParams({
994
+ code,
995
+ client_id: googleClientId,
996
+ client_secret: googleClientSecret,
997
+ redirect_uri: googleRedirectUri,
998
+ grant_type: "authorization_code"
999
+ })
1000
+ });
1001
+ if (!tokenRes.ok) {
1002
+ const errJson = await tokenRes.json().catch(() => ({}));
1003
+ console.error("Google token error", errJson);
1004
+ return res.status(400).json({ ok: false, error: "Failed to exchange Google code" });
1005
+ }
1006
+ const tokenJson = await tokenRes.json();
1007
+ if (!tokenJson.id_token) {
1008
+ return res.status(400).json({ ok: false, error: "Missing id_token from Google" });
1009
+ }
1010
+ const decoded = jwt4.decode(tokenJson.id_token);
1011
+ const email2 = decoded?.email;
1012
+ if (!email2) {
1013
+ return res.status(400).json({ ok: false, error: "Google account has no email" });
1014
+ }
1015
+ const emailVerified = decoded.email_verified ?? true;
1016
+ const firstName = decoded.given_name || "";
1017
+ const lastName = decoded.family_name || "";
1018
+ let user = await OrgUser.findOne({ email: email2 }).lean();
1019
+ if (!user) {
1020
+ const created = await OrgUser.create({
1021
+ email: email2,
1022
+ firstName,
1023
+ lastName,
1024
+ emailVerified,
1025
+ roles: ["platform_user"],
1026
+ projectId: null,
1027
+ metadata: []
1028
+ // you can also store googleId: decoded.sub
1029
+ });
1030
+ user = created.toObject();
1031
+ }
1032
+ const tokens = generateTokens(user);
1033
+ setAuthCookies(res, tokens, cookieConfig);
1034
+ const redirectTo = state ? decodeURIComponent(state) : googleDefaultRedirect;
1035
+ res.redirect(redirectTo);
1036
+ } catch (err) {
1037
+ console.error("Google callback error", err);
1038
+ const redirectError = googleDefaultRedirect.includes("?") ? `${googleDefaultRedirect}&error=google_login_failed` : `${googleDefaultRedirect}?error=google_login_failed`;
1039
+ res.redirect(redirectError);
1040
+ }
1041
+ });
1042
+ r.get("/github", (req, res) => {
1043
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1044
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1045
+ if (!githubClientId || !githubRedirectUri) {
1046
+ return res.status(500).json({ error: "GitHub login not configured" });
1047
+ }
1048
+ const state = req.query.redirectTo ? encodeURIComponent(String(req.query.redirectTo)) : "";
1049
+ const params = new URLSearchParams({
1050
+ client_id: githubClientId,
1051
+ redirect_uri: githubRedirectUri,
1052
+ scope: "user:email",
1053
+ state,
1054
+ allow_signup: "true"
1055
+ });
1056
+ const url = `https://github.com/login/oauth/authorize?${params.toString()}`;
1057
+ res.redirect(url);
1058
+ });
1059
+ r.get("/github/callback", async (req, res) => {
1060
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
1061
+ const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
1062
+ const githubRedirectUri = process.env.GITHUB_REDIRECT_URI;
1063
+ const githubDefaultRedirect = process.env.GITHUB_DEFAULT_REDIRECT || getFrontendBaseUrl(options) || "/";
1064
+ if (!githubClientId || !githubClientSecret || !githubRedirectUri) {
1065
+ return res.status(500).json({ error: "GitHub login not configured" });
1066
+ }
1067
+ const code = String(req.query.code || "");
1068
+ const state = req.query.state ? String(req.query.state) : "";
1069
+ if (!code) {
1070
+ return res.status(400).json({ ok: false, error: "Missing GitHub code" });
1071
+ }
1072
+ try {
1073
+ const tokenRes = await fetch(
1074
+ "https://github.com/login/oauth/access_token",
1075
+ {
1076
+ method: "POST",
1077
+ headers: {
1078
+ Accept: "application/json",
1079
+ "Content-Type": "application/x-www-form-urlencoded"
1080
+ },
1081
+ body: new URLSearchParams({
1082
+ client_id: githubClientId,
1083
+ client_secret: githubClientSecret,
1084
+ code,
1085
+ redirect_uri: githubRedirectUri
1086
+ })
1087
+ }
1088
+ );
1089
+ const tokenJson = await tokenRes.json();
1090
+ if (!tokenJson.access_token) {
1091
+ console.error("GitHub token error:", tokenJson);
1092
+ return res.status(400).json({ ok: false, error: "Failed to get GitHub access token" });
1093
+ }
1094
+ const accessToken = tokenJson.access_token;
1095
+ const userRes = await fetch("https://api.github.com/user", {
1096
+ headers: {
1097
+ Authorization: `Bearer ${accessToken}`,
1098
+ Accept: "application/vnd.github+json"
1099
+ }
1100
+ });
1101
+ const githubUser = await userRes.json();
1102
+ const emailRes = await fetch("https://api.github.com/user/emails", {
1103
+ headers: {
1104
+ Authorization: `Bearer ${accessToken}`,
1105
+ Accept: "application/vnd.github+json"
1106
+ }
1107
+ });
1108
+ const emails = await emailRes.json();
1109
+ const primaryEmail = emails?.find(
1110
+ (e) => e.primary && e.verified
1111
+ )?.email;
1112
+ if (!primaryEmail) {
1113
+ return res.status(400).json({
1114
+ ok: false,
1115
+ error: "GitHub account has no verified email"
1116
+ });
1117
+ }
1118
+ const firstName = githubUser.name?.split(" ")[0] || "";
1119
+ const lastName = githubUser.name?.split(" ").slice(1).join(" ") || "";
1120
+ let user = await OrgUser.findOne({ email: primaryEmail }).lean();
1121
+ if (!user) {
1122
+ const created = await OrgUser.create({
1123
+ email: primaryEmail,
1124
+ firstName,
1125
+ lastName,
1126
+ emailVerified: true,
1127
+ roles: ["platform_user"],
1128
+ projectId: null,
1129
+ metadata: [],
1130
+ githubId: githubUser.id
1131
+ });
1132
+ user = created.toObject();
1133
+ }
1134
+ const tokens = generateTokens(user);
1135
+ setAuthCookies(res, tokens, cookieConfig);
1136
+ const redirectTo = state ? decodeURIComponent(state) : githubDefaultRedirect;
1137
+ res.redirect(redirectTo);
1138
+ } catch (err) {
1139
+ console.error("GitHub callback error:", err);
1140
+ const redirectError = githubDefaultRedirect.includes("?") ? `${githubDefaultRedirect}&error=github_login_failed` : `${githubDefaultRedirect}?error=github_login_failed`;
1141
+ res.redirect(redirectError);
1142
+ }
972
1143
  });
973
1144
  r.get("/get-users", async (req, res) => {
974
1145
  const user = await OrgUser.find({ projectId: req.query.projectId }).lean();