@spfn/auth 0.2.0-beta.64 → 0.2.0-beta.65

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.
package/dist/server.d.ts CHANGED
@@ -3186,10 +3186,10 @@ declare class UsersRepository extends BaseRepository {
3186
3186
  * Write primary 사용
3187
3187
  */
3188
3188
  create(data: NewUser): Promise<{
3189
+ username: string | null;
3190
+ status: "active" | "inactive" | "suspended";
3189
3191
  email: string | null;
3190
3192
  phone: string | null;
3191
- status: "active" | "inactive" | "suspended";
3192
- username: string | null;
3193
3193
  id: number;
3194
3194
  createdAt: Date;
3195
3195
  updatedAt: Date;
@@ -3266,10 +3266,10 @@ declare class UsersRepository extends BaseRepository {
3266
3266
  * Write primary 사용
3267
3267
  */
3268
3268
  deleteById(id: number): Promise<{
3269
+ username: string | null;
3270
+ status: "active" | "inactive" | "suspended";
3269
3271
  email: string | null;
3270
3272
  phone: string | null;
3271
- status: "active" | "inactive" | "suspended";
3272
- username: string | null;
3273
3273
  id: number;
3274
3274
  createdAt: Date;
3275
3275
  updatedAt: Date;
@@ -4345,8 +4345,8 @@ declare class InvitationsRepository extends BaseRepository {
4345
4345
  * 초대 생성
4346
4346
  */
4347
4347
  create(data: NewInvitation): Promise<{
4348
- email: string;
4349
4348
  status: "pending" | "accepted" | "expired" | "cancelled";
4349
+ email: string;
4350
4350
  id: number;
4351
4351
  createdAt: Date;
4352
4352
  updatedAt: Date;
@@ -4379,8 +4379,8 @@ declare class InvitationsRepository extends BaseRepository {
4379
4379
  * 초대 삭제
4380
4380
  */
4381
4381
  deleteById(id: number): Promise<{
4382
- email: string;
4383
4382
  status: "pending" | "accepted" | "expired" | "cancelled";
4383
+ email: string;
4384
4384
  id: number;
4385
4385
  createdAt: Date;
4386
4386
  updatedAt: Date;
@@ -4524,59 +4524,69 @@ declare const invitationsRepository: InvitationsRepository;
4524
4524
  * Social Accounts Repository 클래스
4525
4525
  */
4526
4526
  declare class SocialAccountsRepository extends BaseRepository {
4527
+ /**
4528
+ * 저장 row 의 토큰을 평문으로 복호화해 반환한다.
4529
+ *
4530
+ * 레거시 평문(마커 없음)이 감지되면 즉시 재암호화해 저장하는
4531
+ * self-healing 마이그레이션을 수행한다. 호출자에게는 항상 평문이 반환되어
4532
+ * 외부 API 계약(평문 토큰)이 유지된다.
4533
+ */
4534
+ private decryptAccount;
4527
4535
  /**
4528
4536
  * provider와 providerUserId로 소셜 계정 조회
4529
4537
  * Read replica 사용
4530
4538
  */
4531
4539
  findByProviderAndProviderId(provider: SocialProvider, providerUserId: string): Promise<{
4540
+ accessToken: string | null;
4541
+ refreshToken: string | null;
4542
+ userId: number;
4543
+ id: number;
4532
4544
  createdAt: Date;
4533
4545
  updatedAt: Date;
4534
- id: number;
4535
- userId: number;
4536
4546
  provider: "google" | "github" | "kakao" | "naver" | "superself";
4537
4547
  providerUserId: string;
4538
4548
  providerEmail: string | null;
4539
- accessToken: string | null;
4540
- refreshToken: string | null;
4541
4549
  tokenExpiresAt: Date | null;
4542
- }>;
4550
+ } | null>;
4543
4551
  /**
4544
4552
  * userId로 모든 소셜 계정 조회
4545
4553
  * Read replica 사용
4546
4554
  */
4547
- findByUserId(userId: number): Promise<{
4555
+ findByUserId(userId: number): Promise<({
4556
+ accessToken: string | null;
4557
+ refreshToken: string | null;
4558
+ userId: number;
4559
+ id: number;
4548
4560
  createdAt: Date;
4549
4561
  updatedAt: Date;
4550
- id: number;
4551
- userId: number;
4552
4562
  provider: "google" | "github" | "kakao" | "naver" | "superself";
4553
4563
  providerUserId: string;
4554
4564
  providerEmail: string | null;
4555
- accessToken: string | null;
4556
- refreshToken: string | null;
4557
4565
  tokenExpiresAt: Date | null;
4558
- }[]>;
4566
+ } | null)[]>;
4559
4567
  /**
4560
4568
  * userId와 provider로 소셜 계정 조회
4561
4569
  * Read replica 사용
4562
4570
  */
4563
4571
  findByUserIdAndProvider(userId: number, provider: SocialProvider): Promise<{
4572
+ accessToken: string | null;
4573
+ refreshToken: string | null;
4574
+ userId: number;
4575
+ id: number;
4564
4576
  createdAt: Date;
4565
4577
  updatedAt: Date;
4566
- id: number;
4567
- userId: number;
4568
4578
  provider: "google" | "github" | "kakao" | "naver" | "superself";
4569
4579
  providerUserId: string;
4570
4580
  providerEmail: string | null;
4571
- accessToken: string | null;
4572
- refreshToken: string | null;
4573
4581
  tokenExpiresAt: Date | null;
4574
- }>;
4582
+ } | null>;
4575
4583
  /**
4576
4584
  * 소셜 계정 생성
4577
4585
  * Write primary 사용
4578
4586
  */
4579
4587
  create(data: NewUserSocialAccount): Promise<{
4588
+ accessToken: string | null;
4589
+ refreshToken: string | null;
4580
4590
  userId: number;
4581
4591
  id: number;
4582
4592
  createdAt: Date;
@@ -4584,10 +4594,8 @@ declare class SocialAccountsRepository extends BaseRepository {
4584
4594
  provider: "google" | "github" | "kakao" | "naver" | "superself";
4585
4595
  providerUserId: string;
4586
4596
  providerEmail: string | null;
4587
- accessToken: string | null;
4588
- refreshToken: string | null;
4589
4597
  tokenExpiresAt: Date | null;
4590
- }>;
4598
+ } | null>;
4591
4599
  /**
4592
4600
  * 토큰 정보 업데이트
4593
4601
  * Write primary 사용
@@ -4597,17 +4605,17 @@ declare class SocialAccountsRepository extends BaseRepository {
4597
4605
  refreshToken?: string | null;
4598
4606
  tokenExpiresAt?: Date | null;
4599
4607
  }): Promise<{
4608
+ accessToken: string | null;
4609
+ refreshToken: string | null;
4610
+ userId: number;
4611
+ id: number;
4600
4612
  createdAt: Date;
4601
4613
  updatedAt: Date;
4602
- id: number;
4603
- userId: number;
4604
4614
  provider: "google" | "github" | "kakao" | "naver" | "superself";
4605
4615
  providerUserId: string;
4606
4616
  providerEmail: string | null;
4607
- accessToken: string | null;
4608
- refreshToken: string | null;
4609
4617
  tokenExpiresAt: Date | null;
4610
- }>;
4618
+ } | null>;
4611
4619
  /**
4612
4620
  * 소셜 계정 삭제
4613
4621
  * Write primary 사용
@@ -5095,10 +5103,10 @@ declare function getOptionalAuth(c: Context | {
5095
5103
  declare function getUser(c: Context | {
5096
5104
  raw: Context;
5097
5105
  }): {
5106
+ username: string | null;
5107
+ status: "active" | "inactive" | "suspended";
5098
5108
  email: string | null;
5099
5109
  phone: string | null;
5100
- status: "active" | "inactive" | "suspended";
5101
- username: string | null;
5102
5110
  id: number;
5103
5111
  createdAt: Date;
5104
5112
  updatedAt: Date;
package/dist/server.js CHANGED
@@ -6492,6 +6492,50 @@ var init_invitations_repository = __esm({
6492
6492
  }
6493
6493
  });
6494
6494
 
6495
+ // src/server/lib/oauth/token-cipher.ts
6496
+ import crypto3 from "crypto";
6497
+ import { env as env3 } from "@spfn/auth/config";
6498
+ function getTokenKey() {
6499
+ return crypto3.createHash("sha256").update(`social-token:${env3.SPFN_AUTH_SESSION_SECRET}`).digest();
6500
+ }
6501
+ function isEncrypted(value) {
6502
+ return value.startsWith(ENC_PREFIX);
6503
+ }
6504
+ function encryptToken(plain) {
6505
+ if (isEncrypted(plain)) {
6506
+ return plain;
6507
+ }
6508
+ const iv = crypto3.randomBytes(IV_BYTES);
6509
+ const cipher = crypto3.createCipheriv("aes-256-gcm", getTokenKey(), iv);
6510
+ const ciphertext = Buffer.concat([cipher.update(plain, "utf8"), cipher.final()]);
6511
+ const tag = cipher.getAuthTag();
6512
+ return ENC_PREFIX + Buffer.concat([iv, tag, ciphertext]).toString("base64");
6513
+ }
6514
+ function decryptToken(stored) {
6515
+ if (!isEncrypted(stored)) {
6516
+ return stored;
6517
+ }
6518
+ const packed = Buffer.from(stored.slice(ENC_PREFIX.length), "base64");
6519
+ if (packed.length < IV_BYTES + TAG_BYTES) {
6520
+ throw new Error("Malformed encrypted token: payload too short");
6521
+ }
6522
+ const iv = packed.subarray(0, IV_BYTES);
6523
+ const tag = packed.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);
6524
+ const ciphertext = packed.subarray(IV_BYTES + TAG_BYTES);
6525
+ const decipher = crypto3.createDecipheriv("aes-256-gcm", getTokenKey(), iv);
6526
+ decipher.setAuthTag(tag);
6527
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
6528
+ }
6529
+ var ENC_PREFIX, IV_BYTES, TAG_BYTES;
6530
+ var init_token_cipher = __esm({
6531
+ "src/server/lib/oauth/token-cipher.ts"() {
6532
+ "use strict";
6533
+ ENC_PREFIX = "enc:v1:";
6534
+ IV_BYTES = 12;
6535
+ TAG_BYTES = 16;
6536
+ }
6537
+ });
6538
+
6495
6539
  // src/server/repositories/social-accounts.repository.ts
6496
6540
  import { eq as eq10, and as and7 } from "drizzle-orm";
6497
6541
  import { BaseRepository as BaseRepository10 } from "@spfn/core/db";
@@ -6500,7 +6544,38 @@ var init_social_accounts_repository = __esm({
6500
6544
  "src/server/repositories/social-accounts.repository.ts"() {
6501
6545
  "use strict";
6502
6546
  init_entities();
6547
+ init_token_cipher();
6503
6548
  SocialAccountsRepository = class extends BaseRepository10 {
6549
+ /**
6550
+ * 저장 row 의 토큰을 평문으로 복호화해 반환한다.
6551
+ *
6552
+ * 레거시 평문(마커 없음)이 감지되면 즉시 재암호화해 저장하는
6553
+ * self-healing 마이그레이션을 수행한다. 호출자에게는 항상 평문이 반환되어
6554
+ * 외부 API 계약(평문 토큰)이 유지된다.
6555
+ */
6556
+ async decryptAccount(account) {
6557
+ if (!account) {
6558
+ return account;
6559
+ }
6560
+ const heal = {};
6561
+ if (account.accessToken && !isEncrypted(account.accessToken)) {
6562
+ heal.accessToken = encryptToken(account.accessToken);
6563
+ }
6564
+ if (account.refreshToken && !isEncrypted(account.refreshToken)) {
6565
+ heal.refreshToken = encryptToken(account.refreshToken);
6566
+ }
6567
+ if (heal.accessToken || heal.refreshToken) {
6568
+ try {
6569
+ await this.db.update(userSocialAccounts).set(heal).where(eq10(userSocialAccounts.id, account.id));
6570
+ } catch {
6571
+ }
6572
+ }
6573
+ return {
6574
+ ...account,
6575
+ accessToken: account.accessToken ? decryptToken(account.accessToken) : account.accessToken,
6576
+ refreshToken: account.refreshToken ? decryptToken(account.refreshToken) : account.refreshToken
6577
+ };
6578
+ }
6504
6579
  /**
6505
6580
  * provider와 providerUserId로 소셜 계정 조회
6506
6581
  * Read replica 사용
@@ -6512,14 +6587,15 @@ var init_social_accounts_repository = __esm({
6512
6587
  eq10(userSocialAccounts.providerUserId, providerUserId)
6513
6588
  )
6514
6589
  ).limit(1);
6515
- return result[0] ?? null;
6590
+ return this.decryptAccount(result[0] ?? null);
6516
6591
  }
6517
6592
  /**
6518
6593
  * userId로 모든 소셜 계정 조회
6519
6594
  * Read replica 사용
6520
6595
  */
6521
6596
  async findByUserId(userId) {
6522
- return await this.readDb.select().from(userSocialAccounts).where(eq10(userSocialAccounts.userId, userId));
6597
+ const result = await this.readDb.select().from(userSocialAccounts).where(eq10(userSocialAccounts.userId, userId));
6598
+ return Promise.all(result.map((account) => this.decryptAccount(account)));
6523
6599
  }
6524
6600
  /**
6525
6601
  * userId와 provider로 소셜 계정 조회
@@ -6532,18 +6608,21 @@ var init_social_accounts_repository = __esm({
6532
6608
  eq10(userSocialAccounts.provider, provider)
6533
6609
  )
6534
6610
  ).limit(1);
6535
- return result[0] ?? null;
6611
+ return this.decryptAccount(result[0] ?? null);
6536
6612
  }
6537
6613
  /**
6538
6614
  * 소셜 계정 생성
6539
6615
  * Write primary 사용
6540
6616
  */
6541
6617
  async create(data) {
6542
- return await this._create(userSocialAccounts, {
6618
+ const created = await this._create(userSocialAccounts, {
6543
6619
  ...data,
6620
+ accessToken: data.accessToken ? encryptToken(data.accessToken) : data.accessToken,
6621
+ refreshToken: data.refreshToken ? encryptToken(data.refreshToken) : data.refreshToken,
6544
6622
  createdAt: /* @__PURE__ */ new Date(),
6545
6623
  updatedAt: /* @__PURE__ */ new Date()
6546
6624
  });
6625
+ return this.decryptAccount(created);
6547
6626
  }
6548
6627
  /**
6549
6628
  * 토큰 정보 업데이트
@@ -6552,9 +6631,11 @@ var init_social_accounts_repository = __esm({
6552
6631
  async updateTokens(id11, data) {
6553
6632
  const result = await this.db.update(userSocialAccounts).set({
6554
6633
  ...data,
6634
+ accessToken: data.accessToken ? encryptToken(data.accessToken) : data.accessToken,
6635
+ refreshToken: data.refreshToken ? encryptToken(data.refreshToken) : data.refreshToken,
6555
6636
  updatedAt: /* @__PURE__ */ new Date()
6556
6637
  }).where(eq10(userSocialAccounts.id, id11)).returning();
6557
- return result[0] ?? null;
6638
+ return this.decryptAccount(result[0] ?? null);
6558
6639
  }
6559
6640
  /**
6560
6641
  * 소셜 계정 삭제
@@ -6921,7 +7002,7 @@ import {
6921
7002
  } from "@spfn/auth/errors";
6922
7003
 
6923
7004
  // src/server/services/verification.service.ts
6924
- import { env as env3 } from "@spfn/auth/config";
7005
+ import { env as env4 } from "@spfn/auth/config";
6925
7006
  import { InvalidVerificationCodeError } from "@spfn/auth/errors";
6926
7007
  import jwt2 from "jsonwebtoken";
6927
7008
  import { sendEmail, sendSMS } from "@spfn/notification/server";
@@ -6989,7 +7070,7 @@ async function markCodeAsUsed(codeId) {
6989
7070
  await verificationCodesRepository.markAsUsed(codeId);
6990
7071
  }
6991
7072
  function createVerificationToken(payload) {
6992
- return jwt2.sign(payload, env3.SPFN_AUTH_VERIFICATION_TOKEN_SECRET, {
7073
+ return jwt2.sign(payload, env4.SPFN_AUTH_VERIFICATION_TOKEN_SECRET, {
6993
7074
  expiresIn: VERIFICATION_TOKEN_EXPIRY,
6994
7075
  issuer: "spfn-auth",
6995
7076
  audience: "spfn-client"
@@ -6997,7 +7078,7 @@ function createVerificationToken(payload) {
6997
7078
  }
6998
7079
  function validateVerificationToken(token) {
6999
7080
  try {
7000
- const decoded = jwt2.verify(token, env3.SPFN_AUTH_VERIFICATION_TOKEN_SECRET, {
7081
+ const decoded = jwt2.verify(token, env4.SPFN_AUTH_VERIFICATION_TOKEN_SECRET, {
7001
7082
  issuer: "spfn-auth",
7002
7083
  audience: "spfn-client"
7003
7084
  });
@@ -7142,7 +7223,7 @@ async function revokeKeyService(params) {
7142
7223
  init_repositories();
7143
7224
  import { ValidationError } from "@spfn/core/errors";
7144
7225
  import { ReservedUsernameError, UsernameAlreadyTakenError } from "@spfn/auth/errors";
7145
- import { env as env4 } from "@spfn/auth/config";
7226
+ import { env as env5 } from "@spfn/auth/config";
7146
7227
  async function getUserByIdService(userId) {
7147
7228
  return await usersRepository.findById(userId);
7148
7229
  }
@@ -7159,7 +7240,7 @@ async function updateUserService(userId, updates) {
7159
7240
  await usersRepository.updateById(userId, updates);
7160
7241
  }
7161
7242
  function getReservedUsernames() {
7162
- const raw = env4.SPFN_AUTH_RESERVED_USERNAMES ?? "";
7243
+ const raw = env5.SPFN_AUTH_RESERVED_USERNAMES ?? "";
7163
7244
  if (!raw) {
7164
7245
  return /* @__PURE__ */ new Set();
7165
7246
  }
@@ -7171,8 +7252,8 @@ function isReservedUsername(username) {
7171
7252
  return getReservedUsernames().has(username.toLowerCase());
7172
7253
  }
7173
7254
  function validateUsernameLength(username) {
7174
- const min = env4.SPFN_AUTH_USERNAME_MIN_LENGTH ?? 3;
7175
- const max = env4.SPFN_AUTH_USERNAME_MAX_LENGTH ?? 30;
7255
+ const min = env5.SPFN_AUTH_USERNAME_MIN_LENGTH ?? 3;
7256
+ const max = env5.SPFN_AUTH_USERNAME_MAX_LENGTH ?? 30;
7176
7257
  if (username.length < min) {
7177
7258
  throw new ValidationError({
7178
7259
  message: `Username must be at least ${min} characters`,
@@ -7428,7 +7509,7 @@ init_rbac();
7428
7509
  import { createHash } from "crypto";
7429
7510
 
7430
7511
  // src/server/lib/config.ts
7431
- import { env as env5 } from "@spfn/auth/config";
7512
+ import { env as env6 } from "@spfn/auth/config";
7432
7513
  function getCookieSuffix() {
7433
7514
  const port = process.env.PORT;
7434
7515
  return port ? `_${port}` : "";
@@ -7490,7 +7571,7 @@ function getSessionTtl(override) {
7490
7571
  if (globalConfig.sessionTtl !== void 0) {
7491
7572
  return parseDuration(globalConfig.sessionTtl);
7492
7573
  }
7493
- const envTtl = env5.SPFN_AUTH_SESSION_TTL;
7574
+ const envTtl = env6.SPFN_AUTH_SESSION_TTL;
7494
7575
  if (envTtl) {
7495
7576
  return parseDuration(envTtl);
7496
7577
  }
@@ -7706,9 +7787,9 @@ init_role_service();
7706
7787
 
7707
7788
  // src/server/services/invitation.service.ts
7708
7789
  init_repositories();
7709
- import crypto3 from "crypto";
7790
+ import crypto4 from "crypto";
7710
7791
  function generateInvitationToken() {
7711
- return crypto3.randomUUID();
7792
+ return crypto4.randomUUID();
7712
7793
  }
7713
7794
  function calculateExpiresAt(days = 7) {
7714
7795
  const expiresAt = /* @__PURE__ */ new Date();
@@ -8023,25 +8104,25 @@ async function updateUserProfileService(userId, params) {
8023
8104
 
8024
8105
  // src/server/services/oauth.service.ts
8025
8106
  init_repositories();
8026
- import { env as env8 } from "@spfn/auth/config";
8107
+ import { env as env9 } from "@spfn/auth/config";
8027
8108
  import { ValidationError as ValidationError3 } from "@spfn/core/errors";
8028
8109
 
8029
8110
  // src/server/lib/oauth/google.ts
8030
- import { env as env6 } from "@spfn/auth/config";
8111
+ import { env as env7 } from "@spfn/auth/config";
8031
8112
  var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
8032
8113
  var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
8033
8114
  var GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
8034
8115
  function isGoogleOAuthEnabled() {
8035
- return !!(env6.SPFN_AUTH_GOOGLE_CLIENT_ID && env6.SPFN_AUTH_GOOGLE_CLIENT_SECRET);
8116
+ return !!(env7.SPFN_AUTH_GOOGLE_CLIENT_ID && env7.SPFN_AUTH_GOOGLE_CLIENT_SECRET);
8036
8117
  }
8037
8118
  function getGoogleOAuthConfig() {
8038
- const clientId = env6.SPFN_AUTH_GOOGLE_CLIENT_ID;
8039
- const clientSecret = env6.SPFN_AUTH_GOOGLE_CLIENT_SECRET;
8119
+ const clientId = env7.SPFN_AUTH_GOOGLE_CLIENT_ID;
8120
+ const clientSecret = env7.SPFN_AUTH_GOOGLE_CLIENT_SECRET;
8040
8121
  if (!clientId || !clientSecret) {
8041
8122
  throw new Error("Google OAuth is not configured. Set SPFN_AUTH_GOOGLE_CLIENT_ID and SPFN_AUTH_GOOGLE_CLIENT_SECRET.");
8042
8123
  }
8043
- const baseUrl = env6.NEXT_PUBLIC_SPFN_API_URL || env6.SPFN_API_URL;
8044
- const redirectUri = env6.SPFN_AUTH_GOOGLE_REDIRECT_URI || `${baseUrl}/_auth/oauth/google/callback`;
8124
+ const baseUrl = env7.NEXT_PUBLIC_SPFN_API_URL || env7.SPFN_API_URL;
8125
+ const redirectUri = env7.SPFN_AUTH_GOOGLE_REDIRECT_URI || `${baseUrl}/_auth/oauth/google/callback`;
8045
8126
  return {
8046
8127
  clientId,
8047
8128
  clientSecret,
@@ -8049,7 +8130,7 @@ function getGoogleOAuthConfig() {
8049
8130
  };
8050
8131
  }
8051
8132
  function getDefaultScopes() {
8052
- const envScopes = env6.SPFN_AUTH_GOOGLE_SCOPES;
8133
+ const envScopes = env7.SPFN_AUTH_GOOGLE_SCOPES;
8053
8134
  if (envScopes) {
8054
8135
  return envScopes.split(",").map((s) => s.trim()).filter(Boolean);
8055
8136
  }
@@ -8127,9 +8208,9 @@ async function refreshAccessToken(refreshToken) {
8127
8208
 
8128
8209
  // src/server/lib/oauth/state.ts
8129
8210
  import * as jose from "jose";
8130
- import { env as env7 } from "@spfn/auth/config";
8211
+ import { env as env8 } from "@spfn/auth/config";
8131
8212
  async function getStateKey() {
8132
- const secret = env7.SPFN_AUTH_SESSION_SECRET;
8213
+ const secret = env8.SPFN_AUTH_SESSION_SECRET;
8133
8214
  const encoder = new TextEncoder();
8134
8215
  const data = encoder.encode(`oauth-state:${secret}`);
8135
8216
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
@@ -8282,8 +8363,8 @@ async function oauthCallbackService(params) {
8282
8363
  algorithm: stateData.algorithm
8283
8364
  });
8284
8365
  await updateLastLoginService(userId);
8285
- const appUrl = env8.NEXT_PUBLIC_SPFN_APP_URL || env8.SPFN_APP_URL;
8286
- const callbackPath = env8.SPFN_AUTH_OAUTH_SUCCESS_URL || "/auth/callback";
8366
+ const appUrl = env9.NEXT_PUBLIC_SPFN_APP_URL || env9.SPFN_APP_URL;
8367
+ const callbackPath = env9.SPFN_AUTH_OAUTH_SUCCESS_URL || "/auth/callback";
8287
8368
  const callbackUrl = callbackPath.startsWith("http") ? callbackPath : `${appUrl}${callbackPath}`;
8288
8369
  const redirectUrl = buildRedirectUrl(callbackUrl, {
8289
8370
  userId: String(userId),
@@ -8368,7 +8449,7 @@ function buildRedirectUrl(baseUrl, params) {
8368
8449
  return `${url.pathname}${url.search}`;
8369
8450
  }
8370
8451
  function buildOAuthErrorUrl(error) {
8371
- const errorUrl = env8.SPFN_AUTH_OAUTH_ERROR_URL || "/auth/error?error={error}";
8452
+ const errorUrl = env9.SPFN_AUTH_OAUTH_ERROR_URL || "/auth/error?error={error}";
8372
8453
  return errorUrl.replace("{error}", encodeURIComponent(error));
8373
8454
  }
8374
8455
  function isOAuthProviderEnabled(provider) {
@@ -9518,11 +9599,11 @@ init_types();
9518
9599
  init_schema3();
9519
9600
 
9520
9601
  // src/server/lib/crypto.ts
9521
- import crypto4 from "crypto";
9602
+ import crypto5 from "crypto";
9522
9603
  import jwt3 from "jsonwebtoken";
9523
9604
  function generateKeyPairES256() {
9524
- const keyId = crypto4.randomUUID();
9525
- const { privateKey, publicKey } = crypto4.generateKeyPairSync("ec", {
9605
+ const keyId = crypto5.randomUUID();
9606
+ const { privateKey, publicKey } = crypto5.generateKeyPairSync("ec", {
9526
9607
  namedCurve: "P-256",
9527
9608
  // ES256
9528
9609
  publicKeyEncoding: {
@@ -9536,7 +9617,7 @@ function generateKeyPairES256() {
9536
9617
  });
9537
9618
  const privateKeyB64 = privateKey.toString("base64");
9538
9619
  const publicKeyB64 = publicKey.toString("base64");
9539
- const fingerprint = crypto4.createHash("sha256").update(publicKey).digest("hex");
9620
+ const fingerprint = crypto5.createHash("sha256").update(publicKey).digest("hex");
9540
9621
  return {
9541
9622
  privateKey: privateKeyB64,
9542
9623
  publicKey: publicKeyB64,
@@ -9546,8 +9627,8 @@ function generateKeyPairES256() {
9546
9627
  };
9547
9628
  }
9548
9629
  function generateKeyPairRS256() {
9549
- const keyId = crypto4.randomUUID();
9550
- const { privateKey, publicKey } = crypto4.generateKeyPairSync("rsa", {
9630
+ const keyId = crypto5.randomUUID();
9631
+ const { privateKey, publicKey } = crypto5.generateKeyPairSync("rsa", {
9551
9632
  modulusLength: 2048,
9552
9633
  publicKeyEncoding: {
9553
9634
  type: "spki",
@@ -9560,7 +9641,7 @@ function generateKeyPairRS256() {
9560
9641
  });
9561
9642
  const privateKeyB64 = privateKey.toString("base64");
9562
9643
  const publicKeyB64 = publicKey.toString("base64");
9563
- const fingerprint = crypto4.createHash("sha256").update(publicKey).digest("hex");
9644
+ const fingerprint = crypto5.createHash("sha256").update(publicKey).digest("hex");
9564
9645
  return {
9565
9646
  privateKey: privateKeyB64,
9566
9647
  publicKey: publicKeyB64,
@@ -9575,7 +9656,7 @@ function generateKeyPair(algorithm = "ES256") {
9575
9656
  function generateClientToken(payload, privateKeyB64, algorithm, options) {
9576
9657
  try {
9577
9658
  const privateKeyDER = Buffer.from(privateKeyB64, "base64");
9578
- const privateKeyObject = crypto4.createPrivateKey({
9659
+ const privateKeyObject = crypto5.createPrivateKey({
9579
9660
  key: privateKeyDER,
9580
9661
  format: "der",
9581
9662
  type: "pkcs8"
@@ -9619,10 +9700,10 @@ function shouldRotateKey(createdAt, rotationDays = 90) {
9619
9700
 
9620
9701
  // src/server/lib/session.ts
9621
9702
  import * as jose2 from "jose";
9622
- import { env as env9 } from "@spfn/auth/config";
9703
+ import { env as env10 } from "@spfn/auth/config";
9623
9704
  import { env as coreEnv } from "@spfn/core/config";
9624
9705
  async function getSessionSecretKey() {
9625
- const secret = env9.SPFN_AUTH_SESSION_SECRET;
9706
+ const secret = env10.SPFN_AUTH_SESSION_SECRET;
9626
9707
  const encoder = new TextEncoder();
9627
9708
  const data = encoder.encode(secret);
9628
9709
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
@@ -9704,14 +9785,14 @@ async function shouldRefreshSession(jwt4, thresholdHours = 24) {
9704
9785
  }
9705
9786
 
9706
9787
  // src/server/setup.ts
9707
- import { env as env10 } from "@spfn/auth/config";
9788
+ import { env as env11 } from "@spfn/auth/config";
9708
9789
  import { getRoleByName as getRoleByName2 } from "@spfn/auth/server";
9709
9790
  init_repositories();
9710
9791
  function parseAdminAccounts() {
9711
9792
  const accounts = [];
9712
- if (env10.SPFN_AUTH_ADMIN_ACCOUNTS) {
9793
+ if (env11.SPFN_AUTH_ADMIN_ACCOUNTS) {
9713
9794
  try {
9714
- const accountsJson = env10.SPFN_AUTH_ADMIN_ACCOUNTS;
9795
+ const accountsJson = env11.SPFN_AUTH_ADMIN_ACCOUNTS;
9715
9796
  const parsed = JSON.parse(accountsJson);
9716
9797
  if (!Array.isArray(parsed)) {
9717
9798
  authLogger.setup.error("\u274C SPFN_AUTH_ADMIN_ACCOUNTS must be an array");
@@ -9738,11 +9819,11 @@ function parseAdminAccounts() {
9738
9819
  return accounts;
9739
9820
  }
9740
9821
  }
9741
- const adminEmails = env10.SPFN_AUTH_ADMIN_EMAILS;
9822
+ const adminEmails = env11.SPFN_AUTH_ADMIN_EMAILS;
9742
9823
  if (adminEmails) {
9743
9824
  const emails = adminEmails.split(",").map((s) => s.trim());
9744
- const passwords = (env10.SPFN_AUTH_ADMIN_PASSWORDS || "").split(",").map((s) => s.trim());
9745
- const roles2 = (env10.SPFN_AUTH_ADMIN_ROLES || "").split(",").map((s) => s.trim());
9825
+ const passwords = (env11.SPFN_AUTH_ADMIN_PASSWORDS || "").split(",").map((s) => s.trim());
9826
+ const roles2 = (env11.SPFN_AUTH_ADMIN_ROLES || "").split(",").map((s) => s.trim());
9746
9827
  if (passwords.length !== emails.length) {
9747
9828
  authLogger.setup.error("\u274C SPFN_AUTH_ADMIN_EMAILS and SPFN_AUTH_ADMIN_PASSWORDS length mismatch");
9748
9829
  return accounts;
@@ -9764,8 +9845,8 @@ function parseAdminAccounts() {
9764
9845
  }
9765
9846
  return accounts;
9766
9847
  }
9767
- const adminEmail = env10.SPFN_AUTH_ADMIN_EMAIL;
9768
- const adminPassword = env10.SPFN_AUTH_ADMIN_PASSWORD;
9848
+ const adminEmail = env11.SPFN_AUTH_ADMIN_EMAIL;
9849
+ const adminPassword = env11.SPFN_AUTH_ADMIN_PASSWORD;
9769
9850
  if (adminEmail && adminPassword) {
9770
9851
  accounts.push({
9771
9852
  email: adminEmail,