@spfn/auth 0.2.0-beta.10 → 0.2.0-beta.12
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/README.md +459 -172
- package/dist/{dto-CRlgoCP5.d.ts → authenticate-xfEpwIjH.d.ts} +284 -182
- package/dist/config.d.ts +104 -0
- package/dist/config.js +61 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +187 -130
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/nextjs/api.js +186 -0
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/client.js +80 -0
- package/dist/nextjs/client.js.map +1 -0
- package/dist/nextjs/server.d.ts +68 -2
- package/dist/nextjs/server.js +125 -3
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +243 -366
- package/dist/server.js +596 -476
- package/dist/server.js.map +1 -1
- package/package.json +11 -11
package/dist/server.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
-
}) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
9
3
|
var __esm = (fn, res) => function __init() {
|
|
10
4
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
5
|
};
|
|
@@ -6413,6 +6407,96 @@ var init_invitations_repository = __esm({
|
|
|
6413
6407
|
}
|
|
6414
6408
|
});
|
|
6415
6409
|
|
|
6410
|
+
// src/server/repositories/social-accounts.repository.ts
|
|
6411
|
+
import { eq as eq10, and as and7 } from "drizzle-orm";
|
|
6412
|
+
import { BaseRepository as BaseRepository10 } from "@spfn/core/db";
|
|
6413
|
+
var SocialAccountsRepository, socialAccountsRepository;
|
|
6414
|
+
var init_social_accounts_repository = __esm({
|
|
6415
|
+
"src/server/repositories/social-accounts.repository.ts"() {
|
|
6416
|
+
"use strict";
|
|
6417
|
+
init_entities();
|
|
6418
|
+
SocialAccountsRepository = class extends BaseRepository10 {
|
|
6419
|
+
/**
|
|
6420
|
+
* provider와 providerUserId로 소셜 계정 조회
|
|
6421
|
+
* Read replica 사용
|
|
6422
|
+
*/
|
|
6423
|
+
async findByProviderAndProviderId(provider, providerUserId) {
|
|
6424
|
+
const result = await this.readDb.select().from(userSocialAccounts).where(
|
|
6425
|
+
and7(
|
|
6426
|
+
eq10(userSocialAccounts.provider, provider),
|
|
6427
|
+
eq10(userSocialAccounts.providerUserId, providerUserId)
|
|
6428
|
+
)
|
|
6429
|
+
).limit(1);
|
|
6430
|
+
return result[0] ?? null;
|
|
6431
|
+
}
|
|
6432
|
+
/**
|
|
6433
|
+
* userId로 모든 소셜 계정 조회
|
|
6434
|
+
* Read replica 사용
|
|
6435
|
+
*/
|
|
6436
|
+
async findByUserId(userId) {
|
|
6437
|
+
return await this.readDb.select().from(userSocialAccounts).where(eq10(userSocialAccounts.userId, userId));
|
|
6438
|
+
}
|
|
6439
|
+
/**
|
|
6440
|
+
* userId와 provider로 소셜 계정 조회
|
|
6441
|
+
* Read replica 사용
|
|
6442
|
+
*/
|
|
6443
|
+
async findByUserIdAndProvider(userId, provider) {
|
|
6444
|
+
const result = await this.readDb.select().from(userSocialAccounts).where(
|
|
6445
|
+
and7(
|
|
6446
|
+
eq10(userSocialAccounts.userId, userId),
|
|
6447
|
+
eq10(userSocialAccounts.provider, provider)
|
|
6448
|
+
)
|
|
6449
|
+
).limit(1);
|
|
6450
|
+
return result[0] ?? null;
|
|
6451
|
+
}
|
|
6452
|
+
/**
|
|
6453
|
+
* 소셜 계정 생성
|
|
6454
|
+
* Write primary 사용
|
|
6455
|
+
*/
|
|
6456
|
+
async create(data) {
|
|
6457
|
+
return await this._create(userSocialAccounts, {
|
|
6458
|
+
...data,
|
|
6459
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
6460
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
6461
|
+
});
|
|
6462
|
+
}
|
|
6463
|
+
/**
|
|
6464
|
+
* 토큰 정보 업데이트
|
|
6465
|
+
* Write primary 사용
|
|
6466
|
+
*/
|
|
6467
|
+
async updateTokens(id11, data) {
|
|
6468
|
+
const result = await this.db.update(userSocialAccounts).set({
|
|
6469
|
+
...data,
|
|
6470
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
6471
|
+
}).where(eq10(userSocialAccounts.id, id11)).returning();
|
|
6472
|
+
return result[0] ?? null;
|
|
6473
|
+
}
|
|
6474
|
+
/**
|
|
6475
|
+
* 소셜 계정 삭제
|
|
6476
|
+
* Write primary 사용
|
|
6477
|
+
*/
|
|
6478
|
+
async deleteById(id11) {
|
|
6479
|
+
const result = await this.db.delete(userSocialAccounts).where(eq10(userSocialAccounts.id, id11)).returning();
|
|
6480
|
+
return result[0] ?? null;
|
|
6481
|
+
}
|
|
6482
|
+
/**
|
|
6483
|
+
* userId와 provider로 소셜 계정 삭제
|
|
6484
|
+
* Write primary 사용
|
|
6485
|
+
*/
|
|
6486
|
+
async deleteByUserIdAndProvider(userId, provider) {
|
|
6487
|
+
const result = await this.db.delete(userSocialAccounts).where(
|
|
6488
|
+
and7(
|
|
6489
|
+
eq10(userSocialAccounts.userId, userId),
|
|
6490
|
+
eq10(userSocialAccounts.provider, provider)
|
|
6491
|
+
)
|
|
6492
|
+
).returning();
|
|
6493
|
+
return result[0] ?? null;
|
|
6494
|
+
}
|
|
6495
|
+
};
|
|
6496
|
+
socialAccountsRepository = new SocialAccountsRepository();
|
|
6497
|
+
}
|
|
6498
|
+
});
|
|
6499
|
+
|
|
6416
6500
|
// src/server/repositories/index.ts
|
|
6417
6501
|
var init_repositories = __esm({
|
|
6418
6502
|
"src/server/repositories/index.ts"() {
|
|
@@ -6426,6 +6510,7 @@ var init_repositories = __esm({
|
|
|
6426
6510
|
init_user_permissions_repository();
|
|
6427
6511
|
init_user_profiles_repository();
|
|
6428
6512
|
init_invitations_repository();
|
|
6513
|
+
init_social_accounts_repository();
|
|
6429
6514
|
}
|
|
6430
6515
|
});
|
|
6431
6516
|
|
|
@@ -6552,7 +6637,7 @@ var init_role_service = __esm({
|
|
|
6552
6637
|
import "@spfn/auth/config";
|
|
6553
6638
|
|
|
6554
6639
|
// src/server/routes/index.ts
|
|
6555
|
-
import { defineRouter as
|
|
6640
|
+
import { defineRouter as defineRouter5 } from "@spfn/core/route";
|
|
6556
6641
|
|
|
6557
6642
|
// src/server/routes/auth/index.ts
|
|
6558
6643
|
init_schema3();
|
|
@@ -6701,9 +6786,10 @@ import {
|
|
|
6701
6786
|
} from "@spfn/auth/errors";
|
|
6702
6787
|
|
|
6703
6788
|
// src/server/services/verification.service.ts
|
|
6704
|
-
import { env as
|
|
6789
|
+
import { env as env3 } from "@spfn/auth/config";
|
|
6705
6790
|
import { InvalidVerificationCodeError } from "@spfn/auth/errors";
|
|
6706
6791
|
import jwt2 from "jsonwebtoken";
|
|
6792
|
+
import { sendEmail, sendSMS } from "@spfn/notification/server";
|
|
6707
6793
|
|
|
6708
6794
|
// src/server/logger.ts
|
|
6709
6795
|
import { logger as rootLogger } from "@spfn/core/logger";
|
|
@@ -6723,425 +6809,6 @@ var authLogger = {
|
|
|
6723
6809
|
|
|
6724
6810
|
// src/server/services/verification.service.ts
|
|
6725
6811
|
init_repositories();
|
|
6726
|
-
|
|
6727
|
-
// src/server/services/sms/provider.ts
|
|
6728
|
-
var currentProvider = null;
|
|
6729
|
-
var fallbackProvider = {
|
|
6730
|
-
name: "fallback",
|
|
6731
|
-
sendSMS: async (params) => {
|
|
6732
|
-
authLogger.sms.debug("DEV MODE - SMS not actually sent", {
|
|
6733
|
-
phone: params.phone,
|
|
6734
|
-
message: params.message,
|
|
6735
|
-
purpose: params.purpose || "N/A"
|
|
6736
|
-
});
|
|
6737
|
-
return {
|
|
6738
|
-
success: true,
|
|
6739
|
-
messageId: "dev-mode-no-actual-sms"
|
|
6740
|
-
};
|
|
6741
|
-
}
|
|
6742
|
-
};
|
|
6743
|
-
function registerSMSProvider(provider) {
|
|
6744
|
-
currentProvider = provider;
|
|
6745
|
-
authLogger.sms.info("Registered SMS provider", { name: provider.name });
|
|
6746
|
-
}
|
|
6747
|
-
function getSMSProvider() {
|
|
6748
|
-
return currentProvider || fallbackProvider;
|
|
6749
|
-
}
|
|
6750
|
-
async function sendSMS(params) {
|
|
6751
|
-
const provider = getSMSProvider();
|
|
6752
|
-
return await provider.sendSMS(params);
|
|
6753
|
-
}
|
|
6754
|
-
|
|
6755
|
-
// src/server/services/sms/aws-sns.provider.ts
|
|
6756
|
-
import { env as env3 } from "@spfn/auth/config";
|
|
6757
|
-
function isValidE164Phone(phone) {
|
|
6758
|
-
const e164Regex = /^\+[1-9]\d{1,14}$/;
|
|
6759
|
-
return e164Regex.test(phone);
|
|
6760
|
-
}
|
|
6761
|
-
function createAWSSNSProvider() {
|
|
6762
|
-
try {
|
|
6763
|
-
const { SNSClient, PublishCommand } = __require("@aws-sdk/client-sns");
|
|
6764
|
-
return {
|
|
6765
|
-
name: "aws-sns",
|
|
6766
|
-
sendSMS: async (params) => {
|
|
6767
|
-
const { phone, message, purpose } = params;
|
|
6768
|
-
if (!isValidE164Phone(phone)) {
|
|
6769
|
-
return {
|
|
6770
|
-
success: false,
|
|
6771
|
-
error: "Invalid phone number format. Must be E.164 format (e.g., +821012345678)"
|
|
6772
|
-
};
|
|
6773
|
-
}
|
|
6774
|
-
if (!env3.SPFN_AUTH_AWS_SNS_ACCESS_KEY_ID) {
|
|
6775
|
-
return {
|
|
6776
|
-
success: false,
|
|
6777
|
-
error: "AWS SNS credentials not configured. Set SPFN_AUTH_AWS_SNS_ACCESS_KEY_ID environment variable."
|
|
6778
|
-
};
|
|
6779
|
-
}
|
|
6780
|
-
try {
|
|
6781
|
-
const config = {
|
|
6782
|
-
region: env3.SPFN_AUTH_AWS_REGION || "ap-northeast-2"
|
|
6783
|
-
};
|
|
6784
|
-
if (env3.SPFN_AUTH_AWS_SNS_ACCESS_KEY_ID && env3.SPFN_AUTH_AWS_SNS_SECRET_ACCESS_KEY) {
|
|
6785
|
-
config.credentials = {
|
|
6786
|
-
accessKeyId: env3.SPFN_AUTH_AWS_SNS_ACCESS_KEY_ID,
|
|
6787
|
-
secretAccessKey: env3.SPFN_AUTH_AWS_SNS_SECRET_ACCESS_KEY
|
|
6788
|
-
};
|
|
6789
|
-
}
|
|
6790
|
-
const client = new SNSClient(config);
|
|
6791
|
-
const command = new PublishCommand({
|
|
6792
|
-
PhoneNumber: phone,
|
|
6793
|
-
Message: message,
|
|
6794
|
-
MessageAttributes: {
|
|
6795
|
-
"AWS.SNS.SMS.SMSType": {
|
|
6796
|
-
DataType: "String",
|
|
6797
|
-
StringValue: "Transactional"
|
|
6798
|
-
// For OTP codes
|
|
6799
|
-
},
|
|
6800
|
-
...env3.SPFN_AUTH_AWS_SNS_SENDER_ID && {
|
|
6801
|
-
"AWS.SNS.SMS.SenderID": {
|
|
6802
|
-
DataType: "String",
|
|
6803
|
-
StringValue: env3.SPFN_AUTH_AWS_SNS_SENDER_ID
|
|
6804
|
-
}
|
|
6805
|
-
}
|
|
6806
|
-
}
|
|
6807
|
-
});
|
|
6808
|
-
const response = await client.send(command);
|
|
6809
|
-
authLogger.sms.info("SMS sent via AWS SNS", {
|
|
6810
|
-
phone,
|
|
6811
|
-
messageId: response.MessageId,
|
|
6812
|
-
purpose: purpose || "N/A"
|
|
6813
|
-
});
|
|
6814
|
-
return {
|
|
6815
|
-
success: true,
|
|
6816
|
-
messageId: response.MessageId
|
|
6817
|
-
};
|
|
6818
|
-
} catch (error) {
|
|
6819
|
-
const err = error;
|
|
6820
|
-
authLogger.sms.error("Failed to send SMS via AWS SNS", {
|
|
6821
|
-
phone,
|
|
6822
|
-
error: err.message
|
|
6823
|
-
});
|
|
6824
|
-
return {
|
|
6825
|
-
success: false,
|
|
6826
|
-
error: err.message || "Failed to send SMS via AWS SNS"
|
|
6827
|
-
};
|
|
6828
|
-
}
|
|
6829
|
-
}
|
|
6830
|
-
};
|
|
6831
|
-
} catch (error) {
|
|
6832
|
-
return null;
|
|
6833
|
-
}
|
|
6834
|
-
}
|
|
6835
|
-
var awsSNSProvider = createAWSSNSProvider();
|
|
6836
|
-
|
|
6837
|
-
// src/server/services/sms/index.ts
|
|
6838
|
-
if (awsSNSProvider) {
|
|
6839
|
-
registerSMSProvider(awsSNSProvider);
|
|
6840
|
-
}
|
|
6841
|
-
|
|
6842
|
-
// src/server/services/email/provider.ts
|
|
6843
|
-
var currentProvider2 = null;
|
|
6844
|
-
var fallbackProvider2 = {
|
|
6845
|
-
name: "fallback",
|
|
6846
|
-
sendEmail: async (params) => {
|
|
6847
|
-
authLogger.email.debug("DEV MODE - Email not actually sent", {
|
|
6848
|
-
to: params.to,
|
|
6849
|
-
subject: params.subject,
|
|
6850
|
-
purpose: params.purpose || "N/A",
|
|
6851
|
-
textPreview: params.text?.substring(0, 100) || "N/A"
|
|
6852
|
-
});
|
|
6853
|
-
return {
|
|
6854
|
-
success: true,
|
|
6855
|
-
messageId: "dev-mode-no-actual-email"
|
|
6856
|
-
};
|
|
6857
|
-
}
|
|
6858
|
-
};
|
|
6859
|
-
function registerEmailProvider(provider) {
|
|
6860
|
-
currentProvider2 = provider;
|
|
6861
|
-
authLogger.email.info("Registered email provider", { name: provider.name });
|
|
6862
|
-
}
|
|
6863
|
-
function getEmailProvider() {
|
|
6864
|
-
return currentProvider2 || fallbackProvider2;
|
|
6865
|
-
}
|
|
6866
|
-
async function sendEmail(params) {
|
|
6867
|
-
const provider = getEmailProvider();
|
|
6868
|
-
return await provider.sendEmail(params);
|
|
6869
|
-
}
|
|
6870
|
-
|
|
6871
|
-
// src/server/services/email/aws-ses.provider.ts
|
|
6872
|
-
import { env as env4 } from "@spfn/auth/config";
|
|
6873
|
-
function isValidEmail(email) {
|
|
6874
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
6875
|
-
return emailRegex.test(email);
|
|
6876
|
-
}
|
|
6877
|
-
function createAWSSESProvider() {
|
|
6878
|
-
return {
|
|
6879
|
-
name: "aws-ses",
|
|
6880
|
-
sendEmail: async (params) => {
|
|
6881
|
-
const { to, subject, text: text10, html, purpose } = params;
|
|
6882
|
-
if (!isValidEmail(to)) {
|
|
6883
|
-
return {
|
|
6884
|
-
success: false,
|
|
6885
|
-
error: "Invalid email address format"
|
|
6886
|
-
};
|
|
6887
|
-
}
|
|
6888
|
-
if (!env4.SPFN_AUTH_AWS_SES_ACCESS_KEY_ID) {
|
|
6889
|
-
authLogger.email.warn("AWS SES credentials not configured", {
|
|
6890
|
-
hint: "Set SPFN_AUTH_AWS_SES_ACCESS_KEY_ID environment variable"
|
|
6891
|
-
});
|
|
6892
|
-
return {
|
|
6893
|
-
success: false,
|
|
6894
|
-
error: "AWS SES credentials not configured. Set SPFN_AUTH_AWS_SES_ACCESS_KEY_ID environment variable."
|
|
6895
|
-
};
|
|
6896
|
-
}
|
|
6897
|
-
if (!env4.SPFN_AUTH_AWS_SES_FROM_EMAIL) {
|
|
6898
|
-
authLogger.email.warn("AWS SES sender email not configured", {
|
|
6899
|
-
hint: "Set SPFN_AUTH_AWS_SES_FROM_EMAIL environment variable"
|
|
6900
|
-
});
|
|
6901
|
-
return {
|
|
6902
|
-
success: false,
|
|
6903
|
-
error: "AWS SES sender email not configured. Set SPFN_AUTH_AWS_SES_FROM_EMAIL environment variable."
|
|
6904
|
-
};
|
|
6905
|
-
}
|
|
6906
|
-
let SESClient;
|
|
6907
|
-
let SendEmailCommand;
|
|
6908
|
-
try {
|
|
6909
|
-
const ses = await import("@aws-sdk/client-ses");
|
|
6910
|
-
SESClient = ses.SESClient;
|
|
6911
|
-
SendEmailCommand = ses.SendEmailCommand;
|
|
6912
|
-
} catch (error) {
|
|
6913
|
-
authLogger.email.warn("@aws-sdk/client-ses not installed", {
|
|
6914
|
-
error: error instanceof Error ? error.message : String(error),
|
|
6915
|
-
hint: "Run: pnpm add @aws-sdk/client-ses"
|
|
6916
|
-
});
|
|
6917
|
-
return {
|
|
6918
|
-
success: false,
|
|
6919
|
-
error: "@aws-sdk/client-ses not installed. Run: pnpm add @aws-sdk/client-ses"
|
|
6920
|
-
};
|
|
6921
|
-
}
|
|
6922
|
-
try {
|
|
6923
|
-
const config = {
|
|
6924
|
-
region: env4.SPFN_AUTH_AWS_REGION || "ap-northeast-2"
|
|
6925
|
-
};
|
|
6926
|
-
if (env4.SPFN_AUTH_AWS_SES_ACCESS_KEY_ID && env4.SPFN_AUTH_AWS_SES_SECRET_ACCESS_KEY) {
|
|
6927
|
-
config.credentials = {
|
|
6928
|
-
accessKeyId: env4.SPFN_AUTH_AWS_SES_ACCESS_KEY_ID,
|
|
6929
|
-
secretAccessKey: env4.SPFN_AUTH_AWS_SES_SECRET_ACCESS_KEY
|
|
6930
|
-
};
|
|
6931
|
-
}
|
|
6932
|
-
const client = new SESClient(config);
|
|
6933
|
-
const body = {};
|
|
6934
|
-
if (text10) {
|
|
6935
|
-
body.Text = {
|
|
6936
|
-
Charset: "UTF-8",
|
|
6937
|
-
Data: text10
|
|
6938
|
-
};
|
|
6939
|
-
}
|
|
6940
|
-
if (html) {
|
|
6941
|
-
body.Html = {
|
|
6942
|
-
Charset: "UTF-8",
|
|
6943
|
-
Data: html
|
|
6944
|
-
};
|
|
6945
|
-
}
|
|
6946
|
-
const command = new SendEmailCommand({
|
|
6947
|
-
Source: env4.SPFN_AUTH_AWS_SES_FROM_EMAIL,
|
|
6948
|
-
Destination: {
|
|
6949
|
-
ToAddresses: [to]
|
|
6950
|
-
},
|
|
6951
|
-
Message: {
|
|
6952
|
-
Subject: {
|
|
6953
|
-
Charset: "UTF-8",
|
|
6954
|
-
Data: subject
|
|
6955
|
-
},
|
|
6956
|
-
Body: body
|
|
6957
|
-
}
|
|
6958
|
-
});
|
|
6959
|
-
const response = await client.send(command);
|
|
6960
|
-
authLogger.email.info("Email sent via AWS SES", {
|
|
6961
|
-
to,
|
|
6962
|
-
messageId: response.MessageId,
|
|
6963
|
-
purpose: purpose || "N/A"
|
|
6964
|
-
});
|
|
6965
|
-
return {
|
|
6966
|
-
success: true,
|
|
6967
|
-
messageId: response.MessageId
|
|
6968
|
-
};
|
|
6969
|
-
} catch (error) {
|
|
6970
|
-
const err = error;
|
|
6971
|
-
authLogger.email.error("Failed to send email via AWS SES", {
|
|
6972
|
-
to,
|
|
6973
|
-
error: err.message
|
|
6974
|
-
});
|
|
6975
|
-
return {
|
|
6976
|
-
success: false,
|
|
6977
|
-
error: err.message || "Failed to send email via AWS SES"
|
|
6978
|
-
};
|
|
6979
|
-
}
|
|
6980
|
-
}
|
|
6981
|
-
};
|
|
6982
|
-
}
|
|
6983
|
-
var awsSESProvider = createAWSSESProvider();
|
|
6984
|
-
|
|
6985
|
-
// src/server/services/email/index.ts
|
|
6986
|
-
registerEmailProvider(awsSESProvider);
|
|
6987
|
-
|
|
6988
|
-
// src/server/services/email/templates/verification-code.ts
|
|
6989
|
-
function getSubject(purpose) {
|
|
6990
|
-
switch (purpose) {
|
|
6991
|
-
case "registration":
|
|
6992
|
-
return "Verify your email address";
|
|
6993
|
-
case "login":
|
|
6994
|
-
return "Your login verification code";
|
|
6995
|
-
case "password_reset":
|
|
6996
|
-
return "Reset your password";
|
|
6997
|
-
default:
|
|
6998
|
-
return "Your verification code";
|
|
6999
|
-
}
|
|
7000
|
-
}
|
|
7001
|
-
function getPurposeText(purpose) {
|
|
7002
|
-
switch (purpose) {
|
|
7003
|
-
case "registration":
|
|
7004
|
-
return "complete your registration";
|
|
7005
|
-
case "login":
|
|
7006
|
-
return "verify your identity";
|
|
7007
|
-
case "password_reset":
|
|
7008
|
-
return "reset your password";
|
|
7009
|
-
default:
|
|
7010
|
-
return "verify your identity";
|
|
7011
|
-
}
|
|
7012
|
-
}
|
|
7013
|
-
function generateText(params) {
|
|
7014
|
-
const { code, expiresInMinutes = 5 } = params;
|
|
7015
|
-
return `Your verification code is: ${code}
|
|
7016
|
-
|
|
7017
|
-
This code will expire in ${expiresInMinutes} minutes.
|
|
7018
|
-
|
|
7019
|
-
If you didn't request this code, please ignore this email.`;
|
|
7020
|
-
}
|
|
7021
|
-
function generateHTML(params) {
|
|
7022
|
-
const { code, purpose, expiresInMinutes = 5, appName } = params;
|
|
7023
|
-
const purposeText = getPurposeText(purpose);
|
|
7024
|
-
return `<!DOCTYPE html>
|
|
7025
|
-
<html>
|
|
7026
|
-
<head>
|
|
7027
|
-
<meta charset="utf-8">
|
|
7028
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7029
|
-
<title>Verification Code</title>
|
|
7030
|
-
</head>
|
|
7031
|
-
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #f5f5f5;">
|
|
7032
|
-
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 10px 10px 0 0; text-align: center;">
|
|
7033
|
-
<h1 style="color: white; margin: 0; font-size: 24px;">${appName ? appName : "Verification Code"}</h1>
|
|
7034
|
-
</div>
|
|
7035
|
-
<div style="background: #ffffff; padding: 30px; border: 1px solid #e0e0e0; border-top: none; border-radius: 0 0 10px 10px;">
|
|
7036
|
-
<p style="margin-bottom: 20px; font-size: 16px;">
|
|
7037
|
-
Please use the following verification code to ${purposeText}:
|
|
7038
|
-
</p>
|
|
7039
|
-
<div style="background: #f8f9fa; padding: 25px; border-radius: 8px; text-align: center; margin: 25px 0; border: 2px dashed #dee2e6;">
|
|
7040
|
-
<span style="font-size: 36px; font-weight: bold; letter-spacing: 10px; color: #333; font-family: 'Courier New', monospace;">${code}</span>
|
|
7041
|
-
</div>
|
|
7042
|
-
<p style="color: #666; font-size: 14px; margin-top: 20px; text-align: center;">
|
|
7043
|
-
<strong>This code will expire in ${expiresInMinutes} minutes.</strong>
|
|
7044
|
-
</p>
|
|
7045
|
-
<hr style="border: none; border-top: 1px solid #eee; margin: 30px 0;">
|
|
7046
|
-
<p style="color: #999; font-size: 12px; text-align: center; margin: 0;">
|
|
7047
|
-
If you didn't request this code, please ignore this email.
|
|
7048
|
-
</p>
|
|
7049
|
-
</div>
|
|
7050
|
-
<div style="text-align: center; padding: 20px; color: #999; font-size: 11px;">
|
|
7051
|
-
<p style="margin: 0;">This is an automated message. Please do not reply.</p>
|
|
7052
|
-
</div>
|
|
7053
|
-
</body>
|
|
7054
|
-
</html>`;
|
|
7055
|
-
}
|
|
7056
|
-
function verificationCodeTemplate(params) {
|
|
7057
|
-
return {
|
|
7058
|
-
subject: getSubject(params.purpose),
|
|
7059
|
-
text: generateText(params),
|
|
7060
|
-
html: generateHTML(params)
|
|
7061
|
-
};
|
|
7062
|
-
}
|
|
7063
|
-
|
|
7064
|
-
// src/server/services/email/templates/registry.ts
|
|
7065
|
-
var customTemplates = {};
|
|
7066
|
-
function registerEmailTemplates(templates) {
|
|
7067
|
-
customTemplates = { ...customTemplates, ...templates };
|
|
7068
|
-
authLogger.email.info("Registered custom email templates", {
|
|
7069
|
-
templates: Object.keys(templates)
|
|
7070
|
-
});
|
|
7071
|
-
}
|
|
7072
|
-
function getVerificationCodeTemplate(params) {
|
|
7073
|
-
if (customTemplates.verificationCode) {
|
|
7074
|
-
return customTemplates.verificationCode(params);
|
|
7075
|
-
}
|
|
7076
|
-
return verificationCodeTemplate(params);
|
|
7077
|
-
}
|
|
7078
|
-
function getWelcomeTemplate(params) {
|
|
7079
|
-
if (customTemplates.welcome) {
|
|
7080
|
-
return customTemplates.welcome(params);
|
|
7081
|
-
}
|
|
7082
|
-
return {
|
|
7083
|
-
subject: params.appName ? `Welcome to ${params.appName}!` : "Welcome!",
|
|
7084
|
-
text: `Welcome! Your account has been created successfully.`,
|
|
7085
|
-
html: `
|
|
7086
|
-
<!DOCTYPE html>
|
|
7087
|
-
<html>
|
|
7088
|
-
<head><meta charset="utf-8"></head>
|
|
7089
|
-
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 20px;">
|
|
7090
|
-
<h1>Welcome${params.appName ? ` to ${params.appName}` : ""}!</h1>
|
|
7091
|
-
<p>Your account has been created successfully.</p>
|
|
7092
|
-
</body>
|
|
7093
|
-
</html>`
|
|
7094
|
-
};
|
|
7095
|
-
}
|
|
7096
|
-
function getPasswordResetTemplate(params) {
|
|
7097
|
-
if (customTemplates.passwordReset) {
|
|
7098
|
-
return customTemplates.passwordReset(params);
|
|
7099
|
-
}
|
|
7100
|
-
const expires = params.expiresInMinutes || 30;
|
|
7101
|
-
return {
|
|
7102
|
-
subject: "Reset your password",
|
|
7103
|
-
text: `Click this link to reset your password: ${params.resetLink}
|
|
7104
|
-
|
|
7105
|
-
This link will expire in ${expires} minutes.`,
|
|
7106
|
-
html: `
|
|
7107
|
-
<!DOCTYPE html>
|
|
7108
|
-
<html>
|
|
7109
|
-
<head><meta charset="utf-8"></head>
|
|
7110
|
-
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 20px;">
|
|
7111
|
-
<h1>Reset Your Password</h1>
|
|
7112
|
-
<p>Click the button below to reset your password:</p>
|
|
7113
|
-
<a href="${params.resetLink}" style="display: inline-block; background: #667eea; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px;">Reset Password</a>
|
|
7114
|
-
<p style="color: #666; margin-top: 20px;">This link will expire in ${expires} minutes.</p>
|
|
7115
|
-
</body>
|
|
7116
|
-
</html>`
|
|
7117
|
-
};
|
|
7118
|
-
}
|
|
7119
|
-
function getInvitationTemplate(params) {
|
|
7120
|
-
if (customTemplates.invitation) {
|
|
7121
|
-
return customTemplates.invitation(params);
|
|
7122
|
-
}
|
|
7123
|
-
const appName = params.appName || "our platform";
|
|
7124
|
-
const inviterText = params.inviterName ? `${params.inviterName} has invited you` : "You have been invited";
|
|
7125
|
-
const roleText = params.roleName ? ` as ${params.roleName}` : "";
|
|
7126
|
-
return {
|
|
7127
|
-
subject: `You're invited to join ${appName}`,
|
|
7128
|
-
text: `${inviterText} to join ${appName}${roleText}.
|
|
7129
|
-
|
|
7130
|
-
Click here to accept: ${params.inviteLink}`,
|
|
7131
|
-
html: `
|
|
7132
|
-
<!DOCTYPE html>
|
|
7133
|
-
<html>
|
|
7134
|
-
<head><meta charset="utf-8"></head>
|
|
7135
|
-
<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 20px;">
|
|
7136
|
-
<h1>You're Invited!</h1>
|
|
7137
|
-
<p>${inviterText} to join <strong>${appName}</strong>${roleText}.</p>
|
|
7138
|
-
<a href="${params.inviteLink}" style="display: inline-block; background: #667eea; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px;">Accept Invitation</a>
|
|
7139
|
-
</body>
|
|
7140
|
-
</html>`
|
|
7141
|
-
};
|
|
7142
|
-
}
|
|
7143
|
-
|
|
7144
|
-
// src/server/services/verification.service.ts
|
|
7145
6812
|
var VERIFICATION_TOKEN_EXPIRY = "15m";
|
|
7146
6813
|
var VERIFICATION_CODE_EXPIRY_MINUTES = 5;
|
|
7147
6814
|
var MAX_VERIFICATION_ATTEMPTS = 5;
|
|
@@ -7185,7 +6852,7 @@ async function markCodeAsUsed(codeId) {
|
|
|
7185
6852
|
await verificationCodesRepository.markAsUsed(codeId);
|
|
7186
6853
|
}
|
|
7187
6854
|
function createVerificationToken(payload) {
|
|
7188
|
-
return jwt2.sign(payload,
|
|
6855
|
+
return jwt2.sign(payload, env3.SPFN_AUTH_VERIFICATION_TOKEN_SECRET, {
|
|
7189
6856
|
expiresIn: VERIFICATION_TOKEN_EXPIRY,
|
|
7190
6857
|
issuer: "spfn-auth",
|
|
7191
6858
|
audience: "spfn-client"
|
|
@@ -7193,7 +6860,7 @@ function createVerificationToken(payload) {
|
|
|
7193
6860
|
}
|
|
7194
6861
|
function validateVerificationToken(token) {
|
|
7195
6862
|
try {
|
|
7196
|
-
const decoded = jwt2.verify(token,
|
|
6863
|
+
const decoded = jwt2.verify(token, env3.SPFN_AUTH_VERIFICATION_TOKEN_SECRET, {
|
|
7197
6864
|
issuer: "spfn-auth",
|
|
7198
6865
|
audience: "spfn-client"
|
|
7199
6866
|
});
|
|
@@ -7207,17 +6874,14 @@ function validateVerificationToken(token) {
|
|
|
7207
6874
|
}
|
|
7208
6875
|
}
|
|
7209
6876
|
async function sendVerificationEmail(email, code, purpose) {
|
|
7210
|
-
const { subject, text: text10, html } = getVerificationCodeTemplate({
|
|
7211
|
-
code,
|
|
7212
|
-
purpose,
|
|
7213
|
-
expiresInMinutes: VERIFICATION_CODE_EXPIRY_MINUTES
|
|
7214
|
-
});
|
|
7215
6877
|
const result = await sendEmail({
|
|
7216
6878
|
to: email,
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
6879
|
+
template: "verification-code",
|
|
6880
|
+
data: {
|
|
6881
|
+
code,
|
|
6882
|
+
purpose,
|
|
6883
|
+
expiresInMinutes: VERIFICATION_CODE_EXPIRY_MINUTES
|
|
6884
|
+
}
|
|
7221
6885
|
});
|
|
7222
6886
|
if (!result.success) {
|
|
7223
6887
|
authLogger.email.error("Failed to send verification email", {
|
|
@@ -7228,11 +6892,13 @@ async function sendVerificationEmail(email, code, purpose) {
|
|
|
7228
6892
|
}
|
|
7229
6893
|
}
|
|
7230
6894
|
async function sendVerificationSMS(phone, code, purpose) {
|
|
7231
|
-
const message = `Your verification code is: ${code}`;
|
|
7232
6895
|
const result = await sendSMS({
|
|
7233
|
-
phone,
|
|
7234
|
-
|
|
7235
|
-
|
|
6896
|
+
to: phone,
|
|
6897
|
+
template: "verification-code",
|
|
6898
|
+
data: {
|
|
6899
|
+
code,
|
|
6900
|
+
expiresInMinutes: VERIFICATION_CODE_EXPIRY_MINUTES
|
|
6901
|
+
}
|
|
7236
6902
|
});
|
|
7237
6903
|
if (!result.success) {
|
|
7238
6904
|
authLogger.sms.error("Failed to send verification SMS", {
|
|
@@ -7495,12 +7161,14 @@ init_repositories();
|
|
|
7495
7161
|
init_rbac();
|
|
7496
7162
|
|
|
7497
7163
|
// src/server/lib/config.ts
|
|
7498
|
-
import { env as
|
|
7164
|
+
import { env as env4 } from "@spfn/auth/config";
|
|
7499
7165
|
var COOKIE_NAMES = {
|
|
7500
7166
|
/** Encrypted session data (userId, privateKey, keyId, algorithm) */
|
|
7501
7167
|
SESSION: "spfn_session",
|
|
7502
7168
|
/** Current key ID (for key rotation) */
|
|
7503
|
-
SESSION_KEY_ID: "spfn_session_key_id"
|
|
7169
|
+
SESSION_KEY_ID: "spfn_session_key_id",
|
|
7170
|
+
/** Pending OAuth session (privateKey, keyId, algorithm) - temporary during OAuth flow */
|
|
7171
|
+
OAUTH_PENDING: "spfn_oauth_pending"
|
|
7504
7172
|
};
|
|
7505
7173
|
function parseDuration(duration) {
|
|
7506
7174
|
if (typeof duration === "number") {
|
|
@@ -7545,7 +7213,7 @@ function getSessionTtl(override) {
|
|
|
7545
7213
|
if (globalConfig.sessionTtl !== void 0) {
|
|
7546
7214
|
return parseDuration(globalConfig.sessionTtl);
|
|
7547
7215
|
}
|
|
7548
|
-
const envTtl =
|
|
7216
|
+
const envTtl = env4.SPFN_AUTH_SESSION_TTL;
|
|
7549
7217
|
if (envTtl) {
|
|
7550
7218
|
return parseDuration(envTtl);
|
|
7551
7219
|
}
|
|
@@ -7985,6 +7653,301 @@ async function updateUserProfileService(userId, params) {
|
|
|
7985
7653
|
return profile;
|
|
7986
7654
|
}
|
|
7987
7655
|
|
|
7656
|
+
// src/server/services/oauth.service.ts
|
|
7657
|
+
init_repositories();
|
|
7658
|
+
import { env as env7 } from "@spfn/auth/config";
|
|
7659
|
+
import { ValidationError as ValidationError2 } from "@spfn/core/errors";
|
|
7660
|
+
|
|
7661
|
+
// src/server/lib/oauth/google.ts
|
|
7662
|
+
import { env as env5 } from "@spfn/auth/config";
|
|
7663
|
+
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
7664
|
+
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
7665
|
+
var GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
7666
|
+
function isGoogleOAuthEnabled() {
|
|
7667
|
+
return !!(env5.SPFN_AUTH_GOOGLE_CLIENT_ID && env5.SPFN_AUTH_GOOGLE_CLIENT_SECRET);
|
|
7668
|
+
}
|
|
7669
|
+
function getGoogleOAuthConfig() {
|
|
7670
|
+
const clientId = env5.SPFN_AUTH_GOOGLE_CLIENT_ID;
|
|
7671
|
+
const clientSecret = env5.SPFN_AUTH_GOOGLE_CLIENT_SECRET;
|
|
7672
|
+
if (!clientId || !clientSecret) {
|
|
7673
|
+
throw new Error("Google OAuth is not configured. Set SPFN_AUTH_GOOGLE_CLIENT_ID and SPFN_AUTH_GOOGLE_CLIENT_SECRET.");
|
|
7674
|
+
}
|
|
7675
|
+
const redirectUri = env5.SPFN_AUTH_GOOGLE_REDIRECT_URI || `${env5.SPFN_API_URL}/_auth/oauth/google/callback`;
|
|
7676
|
+
return {
|
|
7677
|
+
clientId,
|
|
7678
|
+
clientSecret,
|
|
7679
|
+
redirectUri
|
|
7680
|
+
};
|
|
7681
|
+
}
|
|
7682
|
+
function getGoogleAuthUrl(state, scopes = ["email", "profile"]) {
|
|
7683
|
+
const config = getGoogleOAuthConfig();
|
|
7684
|
+
const params = new URLSearchParams({
|
|
7685
|
+
client_id: config.clientId,
|
|
7686
|
+
redirect_uri: config.redirectUri,
|
|
7687
|
+
response_type: "code",
|
|
7688
|
+
scope: scopes.join(" "),
|
|
7689
|
+
state,
|
|
7690
|
+
access_type: "offline",
|
|
7691
|
+
// refresh_token 받기 위해
|
|
7692
|
+
prompt: "consent"
|
|
7693
|
+
// 매번 동의 화면 표시 (refresh_token 보장)
|
|
7694
|
+
});
|
|
7695
|
+
return `${GOOGLE_AUTH_URL}?${params.toString()}`;
|
|
7696
|
+
}
|
|
7697
|
+
async function exchangeCodeForTokens(code) {
|
|
7698
|
+
const config = getGoogleOAuthConfig();
|
|
7699
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
7700
|
+
method: "POST",
|
|
7701
|
+
headers: {
|
|
7702
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
7703
|
+
},
|
|
7704
|
+
body: new URLSearchParams({
|
|
7705
|
+
client_id: config.clientId,
|
|
7706
|
+
client_secret: config.clientSecret,
|
|
7707
|
+
redirect_uri: config.redirectUri,
|
|
7708
|
+
grant_type: "authorization_code",
|
|
7709
|
+
code
|
|
7710
|
+
})
|
|
7711
|
+
});
|
|
7712
|
+
if (!response.ok) {
|
|
7713
|
+
const error = await response.text();
|
|
7714
|
+
throw new Error(`Failed to exchange code for tokens: ${error}`);
|
|
7715
|
+
}
|
|
7716
|
+
return response.json();
|
|
7717
|
+
}
|
|
7718
|
+
async function getGoogleUserInfo(accessToken) {
|
|
7719
|
+
const response = await fetch(GOOGLE_USERINFO_URL, {
|
|
7720
|
+
headers: {
|
|
7721
|
+
Authorization: `Bearer ${accessToken}`
|
|
7722
|
+
}
|
|
7723
|
+
});
|
|
7724
|
+
if (!response.ok) {
|
|
7725
|
+
const error = await response.text();
|
|
7726
|
+
throw new Error(`Failed to get user info: ${error}`);
|
|
7727
|
+
}
|
|
7728
|
+
return response.json();
|
|
7729
|
+
}
|
|
7730
|
+
async function refreshAccessToken(refreshToken) {
|
|
7731
|
+
const config = getGoogleOAuthConfig();
|
|
7732
|
+
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
7733
|
+
method: "POST",
|
|
7734
|
+
headers: {
|
|
7735
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
7736
|
+
},
|
|
7737
|
+
body: new URLSearchParams({
|
|
7738
|
+
client_id: config.clientId,
|
|
7739
|
+
client_secret: config.clientSecret,
|
|
7740
|
+
refresh_token: refreshToken,
|
|
7741
|
+
grant_type: "refresh_token"
|
|
7742
|
+
})
|
|
7743
|
+
});
|
|
7744
|
+
if (!response.ok) {
|
|
7745
|
+
const error = await response.text();
|
|
7746
|
+
throw new Error(`Failed to refresh access token: ${error}`);
|
|
7747
|
+
}
|
|
7748
|
+
return response.json();
|
|
7749
|
+
}
|
|
7750
|
+
|
|
7751
|
+
// src/server/lib/oauth/state.ts
|
|
7752
|
+
import * as jose from "jose";
|
|
7753
|
+
import { env as env6 } from "@spfn/auth/config";
|
|
7754
|
+
async function getStateKey() {
|
|
7755
|
+
const secret = env6.SPFN_AUTH_SESSION_SECRET;
|
|
7756
|
+
const encoder = new TextEncoder();
|
|
7757
|
+
const data = encoder.encode(`oauth-state:${secret}`);
|
|
7758
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
7759
|
+
return new Uint8Array(hashBuffer);
|
|
7760
|
+
}
|
|
7761
|
+
function generateNonce() {
|
|
7762
|
+
const array = new Uint8Array(16);
|
|
7763
|
+
crypto.getRandomValues(array);
|
|
7764
|
+
return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
7765
|
+
}
|
|
7766
|
+
async function createOAuthState(params) {
|
|
7767
|
+
const key = await getStateKey();
|
|
7768
|
+
const state = {
|
|
7769
|
+
returnUrl: params.returnUrl,
|
|
7770
|
+
nonce: generateNonce(),
|
|
7771
|
+
provider: params.provider,
|
|
7772
|
+
publicKey: params.publicKey,
|
|
7773
|
+
keyId: params.keyId,
|
|
7774
|
+
fingerprint: params.fingerprint,
|
|
7775
|
+
algorithm: params.algorithm
|
|
7776
|
+
};
|
|
7777
|
+
const jwe = await new jose.EncryptJWT({ state }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime("10m").encrypt(key);
|
|
7778
|
+
return encodeURIComponent(jwe);
|
|
7779
|
+
}
|
|
7780
|
+
async function verifyOAuthState(encryptedState) {
|
|
7781
|
+
const key = await getStateKey();
|
|
7782
|
+
const jwe = decodeURIComponent(encryptedState);
|
|
7783
|
+
const { payload } = await jose.jwtDecrypt(jwe, key);
|
|
7784
|
+
return payload.state;
|
|
7785
|
+
}
|
|
7786
|
+
|
|
7787
|
+
// src/server/services/oauth.service.ts
|
|
7788
|
+
async function oauthStartService(params) {
|
|
7789
|
+
const { provider, returnUrl, publicKey, keyId, fingerprint, algorithm } = params;
|
|
7790
|
+
if (provider === "google") {
|
|
7791
|
+
if (!isGoogleOAuthEnabled()) {
|
|
7792
|
+
throw new ValidationError2({
|
|
7793
|
+
message: "Google OAuth is not configured. Set SPFN_AUTH_GOOGLE_CLIENT_ID and SPFN_AUTH_GOOGLE_CLIENT_SECRET."
|
|
7794
|
+
});
|
|
7795
|
+
}
|
|
7796
|
+
const state = await createOAuthState({
|
|
7797
|
+
provider: "google",
|
|
7798
|
+
returnUrl,
|
|
7799
|
+
publicKey,
|
|
7800
|
+
keyId,
|
|
7801
|
+
fingerprint,
|
|
7802
|
+
algorithm
|
|
7803
|
+
});
|
|
7804
|
+
const authUrl = getGoogleAuthUrl(state);
|
|
7805
|
+
return { authUrl };
|
|
7806
|
+
}
|
|
7807
|
+
throw new ValidationError2({
|
|
7808
|
+
message: `Unsupported OAuth provider: ${provider}`
|
|
7809
|
+
});
|
|
7810
|
+
}
|
|
7811
|
+
async function oauthCallbackService(params) {
|
|
7812
|
+
const { provider, code, state } = params;
|
|
7813
|
+
const stateData = await verifyOAuthState(state);
|
|
7814
|
+
if (stateData.provider !== provider) {
|
|
7815
|
+
throw new ValidationError2({
|
|
7816
|
+
message: "OAuth state provider mismatch"
|
|
7817
|
+
});
|
|
7818
|
+
}
|
|
7819
|
+
if (provider === "google") {
|
|
7820
|
+
return handleGoogleCallback(code, stateData);
|
|
7821
|
+
}
|
|
7822
|
+
throw new ValidationError2({
|
|
7823
|
+
message: `Unsupported OAuth provider: ${provider}`
|
|
7824
|
+
});
|
|
7825
|
+
}
|
|
7826
|
+
async function handleGoogleCallback(code, stateData) {
|
|
7827
|
+
const tokens = await exchangeCodeForTokens(code);
|
|
7828
|
+
const googleUser = await getGoogleUserInfo(tokens.access_token);
|
|
7829
|
+
const existingSocialAccount = await socialAccountsRepository.findByProviderAndProviderId(
|
|
7830
|
+
"google",
|
|
7831
|
+
googleUser.id
|
|
7832
|
+
);
|
|
7833
|
+
let userId;
|
|
7834
|
+
let isNewUser = false;
|
|
7835
|
+
if (existingSocialAccount) {
|
|
7836
|
+
userId = existingSocialAccount.userId;
|
|
7837
|
+
await socialAccountsRepository.updateTokens(existingSocialAccount.id, {
|
|
7838
|
+
accessToken: tokens.access_token,
|
|
7839
|
+
refreshToken: tokens.refresh_token ?? existingSocialAccount.refreshToken,
|
|
7840
|
+
tokenExpiresAt: new Date(Date.now() + tokens.expires_in * 1e3)
|
|
7841
|
+
});
|
|
7842
|
+
} else {
|
|
7843
|
+
const result = await createOrLinkUser(googleUser, tokens);
|
|
7844
|
+
userId = result.userId;
|
|
7845
|
+
isNewUser = result.isNewUser;
|
|
7846
|
+
}
|
|
7847
|
+
await registerPublicKeyService({
|
|
7848
|
+
userId,
|
|
7849
|
+
keyId: stateData.keyId,
|
|
7850
|
+
publicKey: stateData.publicKey,
|
|
7851
|
+
fingerprint: stateData.fingerprint,
|
|
7852
|
+
algorithm: stateData.algorithm
|
|
7853
|
+
});
|
|
7854
|
+
await updateLastLoginService(userId);
|
|
7855
|
+
const appUrl = env7.SPFN_APP_URL;
|
|
7856
|
+
const callbackPath = env7.SPFN_AUTH_OAUTH_SUCCESS_URL || "/auth/callback";
|
|
7857
|
+
const callbackUrl = callbackPath.startsWith("http") ? callbackPath : `${appUrl}${callbackPath}`;
|
|
7858
|
+
const redirectUrl = buildRedirectUrl(callbackUrl, {
|
|
7859
|
+
userId: String(userId),
|
|
7860
|
+
keyId: stateData.keyId,
|
|
7861
|
+
returnUrl: stateData.returnUrl,
|
|
7862
|
+
isNewUser: String(isNewUser)
|
|
7863
|
+
});
|
|
7864
|
+
return {
|
|
7865
|
+
redirectUrl,
|
|
7866
|
+
userId: String(userId),
|
|
7867
|
+
keyId: stateData.keyId,
|
|
7868
|
+
isNewUser
|
|
7869
|
+
};
|
|
7870
|
+
}
|
|
7871
|
+
async function createOrLinkUser(googleUser, tokens) {
|
|
7872
|
+
const existingUser = googleUser.email ? await usersRepository.findByEmail(googleUser.email) : null;
|
|
7873
|
+
let userId;
|
|
7874
|
+
let isNewUser = false;
|
|
7875
|
+
if (existingUser) {
|
|
7876
|
+
if (!googleUser.verified_email) {
|
|
7877
|
+
throw new ValidationError2({
|
|
7878
|
+
message: "Cannot link to existing account with unverified email. Please verify your email with Google first."
|
|
7879
|
+
});
|
|
7880
|
+
}
|
|
7881
|
+
userId = existingUser.id;
|
|
7882
|
+
if (!existingUser.emailVerifiedAt) {
|
|
7883
|
+
await usersRepository.updateById(existingUser.id, {
|
|
7884
|
+
emailVerifiedAt: /* @__PURE__ */ new Date()
|
|
7885
|
+
});
|
|
7886
|
+
}
|
|
7887
|
+
} else {
|
|
7888
|
+
const { getRoleByName: getRoleByName3 } = await Promise.resolve().then(() => (init_role_service(), role_service_exports));
|
|
7889
|
+
const userRole = await getRoleByName3("user");
|
|
7890
|
+
if (!userRole) {
|
|
7891
|
+
throw new Error("Default user role not found. Run initializeAuth() first.");
|
|
7892
|
+
}
|
|
7893
|
+
const newUser = await usersRepository.create({
|
|
7894
|
+
email: googleUser.verified_email ? googleUser.email : null,
|
|
7895
|
+
phone: null,
|
|
7896
|
+
passwordHash: null,
|
|
7897
|
+
// OAuth 사용자는 비밀번호 없음
|
|
7898
|
+
passwordChangeRequired: false,
|
|
7899
|
+
roleId: userRole.id,
|
|
7900
|
+
status: "active",
|
|
7901
|
+
emailVerifiedAt: googleUser.verified_email ? /* @__PURE__ */ new Date() : null
|
|
7902
|
+
});
|
|
7903
|
+
userId = newUser.id;
|
|
7904
|
+
isNewUser = true;
|
|
7905
|
+
}
|
|
7906
|
+
await socialAccountsRepository.create({
|
|
7907
|
+
userId,
|
|
7908
|
+
provider: "google",
|
|
7909
|
+
providerUserId: googleUser.id,
|
|
7910
|
+
providerEmail: googleUser.email,
|
|
7911
|
+
accessToken: tokens.access_token,
|
|
7912
|
+
refreshToken: tokens.refresh_token ?? null,
|
|
7913
|
+
tokenExpiresAt: new Date(Date.now() + tokens.expires_in * 1e3)
|
|
7914
|
+
});
|
|
7915
|
+
return { userId, isNewUser };
|
|
7916
|
+
}
|
|
7917
|
+
function buildRedirectUrl(baseUrl, params) {
|
|
7918
|
+
const url = new URL(baseUrl, "http://placeholder");
|
|
7919
|
+
for (const [key, value] of Object.entries(params)) {
|
|
7920
|
+
url.searchParams.set(key, value);
|
|
7921
|
+
}
|
|
7922
|
+
if (baseUrl.startsWith("http")) {
|
|
7923
|
+
return url.toString();
|
|
7924
|
+
}
|
|
7925
|
+
return `${url.pathname}${url.search}`;
|
|
7926
|
+
}
|
|
7927
|
+
function buildOAuthErrorUrl(error) {
|
|
7928
|
+
const errorUrl = env7.SPFN_AUTH_OAUTH_ERROR_URL || "/auth/error?error={error}";
|
|
7929
|
+
return errorUrl.replace("{error}", encodeURIComponent(error));
|
|
7930
|
+
}
|
|
7931
|
+
function isOAuthProviderEnabled(provider) {
|
|
7932
|
+
switch (provider) {
|
|
7933
|
+
case "google":
|
|
7934
|
+
return isGoogleOAuthEnabled();
|
|
7935
|
+
case "github":
|
|
7936
|
+
case "kakao":
|
|
7937
|
+
case "naver":
|
|
7938
|
+
return false;
|
|
7939
|
+
default:
|
|
7940
|
+
return false;
|
|
7941
|
+
}
|
|
7942
|
+
}
|
|
7943
|
+
function getEnabledOAuthProviders() {
|
|
7944
|
+
const providers = [];
|
|
7945
|
+
if (isGoogleOAuthEnabled()) {
|
|
7946
|
+
providers.push("google");
|
|
7947
|
+
}
|
|
7948
|
+
return providers;
|
|
7949
|
+
}
|
|
7950
|
+
|
|
7988
7951
|
// src/server/routes/auth/index.ts
|
|
7989
7952
|
init_esm();
|
|
7990
7953
|
import { Transactional } from "@spfn/core/db";
|
|
@@ -8595,12 +8558,163 @@ var userRouter = defineRouter3({
|
|
|
8595
8558
|
updateUserProfile
|
|
8596
8559
|
});
|
|
8597
8560
|
|
|
8561
|
+
// src/server/routes/oauth/index.ts
|
|
8562
|
+
init_esm();
|
|
8563
|
+
init_types();
|
|
8564
|
+
import { Transactional as Transactional2 } from "@spfn/core/db";
|
|
8565
|
+
import { defineRouter as defineRouter4, route as route4 } from "@spfn/core/route";
|
|
8566
|
+
var oauthGoogleStart = route4.get("/_auth/oauth/google").input({
|
|
8567
|
+
query: Type.Object({
|
|
8568
|
+
state: Type.String({
|
|
8569
|
+
description: "Encrypted OAuth state (returnUrl, publicKey, keyId, fingerprint, algorithm)"
|
|
8570
|
+
})
|
|
8571
|
+
})
|
|
8572
|
+
}).skip(["auth"]).handler(async (c) => {
|
|
8573
|
+
const { query } = await c.data();
|
|
8574
|
+
if (!isGoogleOAuthEnabled()) {
|
|
8575
|
+
return c.redirect(buildOAuthErrorUrl("Google OAuth is not configured"));
|
|
8576
|
+
}
|
|
8577
|
+
const authUrl = getGoogleAuthUrl(query.state);
|
|
8578
|
+
return c.redirect(authUrl);
|
|
8579
|
+
});
|
|
8580
|
+
var oauthGoogleCallback = route4.get("/_auth/oauth/google/callback").input({
|
|
8581
|
+
query: Type.Object({
|
|
8582
|
+
code: Type.Optional(Type.String({
|
|
8583
|
+
description: "Authorization code from Google"
|
|
8584
|
+
})),
|
|
8585
|
+
state: Type.Optional(Type.String({
|
|
8586
|
+
description: "OAuth state parameter"
|
|
8587
|
+
})),
|
|
8588
|
+
error: Type.Optional(Type.String({
|
|
8589
|
+
description: "Error code from Google"
|
|
8590
|
+
})),
|
|
8591
|
+
error_description: Type.Optional(Type.String({
|
|
8592
|
+
description: "Error description from Google"
|
|
8593
|
+
}))
|
|
8594
|
+
})
|
|
8595
|
+
}).use([Transactional2()]).skip(["auth"]).handler(async (c) => {
|
|
8596
|
+
const { query } = await c.data();
|
|
8597
|
+
if (query.error) {
|
|
8598
|
+
const errorMessage = query.error_description || query.error;
|
|
8599
|
+
return c.redirect(buildOAuthErrorUrl(errorMessage));
|
|
8600
|
+
}
|
|
8601
|
+
if (!query.code || !query.state) {
|
|
8602
|
+
return c.redirect(buildOAuthErrorUrl("Missing authorization code or state"));
|
|
8603
|
+
}
|
|
8604
|
+
try {
|
|
8605
|
+
const result = await oauthCallbackService({
|
|
8606
|
+
provider: "google",
|
|
8607
|
+
code: query.code,
|
|
8608
|
+
state: query.state
|
|
8609
|
+
});
|
|
8610
|
+
return c.redirect(result.redirectUrl);
|
|
8611
|
+
} catch (err) {
|
|
8612
|
+
const message = err instanceof Error ? err.message : "OAuth callback failed";
|
|
8613
|
+
return c.redirect(buildOAuthErrorUrl(message));
|
|
8614
|
+
}
|
|
8615
|
+
});
|
|
8616
|
+
var oauthStart = route4.post("/_auth/oauth/start").input({
|
|
8617
|
+
body: Type.Object({
|
|
8618
|
+
provider: Type.Union(SOCIAL_PROVIDERS.map((p) => Type.Literal(p)), {
|
|
8619
|
+
description: "OAuth provider (google, github, kakao, naver)"
|
|
8620
|
+
}),
|
|
8621
|
+
returnUrl: Type.String({
|
|
8622
|
+
description: "URL to redirect after OAuth success"
|
|
8623
|
+
}),
|
|
8624
|
+
publicKey: Type.String({
|
|
8625
|
+
description: "Client public key (Base64 DER)"
|
|
8626
|
+
}),
|
|
8627
|
+
keyId: Type.String({
|
|
8628
|
+
description: "Key identifier (UUID)"
|
|
8629
|
+
}),
|
|
8630
|
+
fingerprint: Type.String({
|
|
8631
|
+
description: "Key fingerprint (SHA-256 hex)"
|
|
8632
|
+
}),
|
|
8633
|
+
algorithm: Type.Union(KEY_ALGORITHM.map((a) => Type.Literal(a)), {
|
|
8634
|
+
description: "Key algorithm (ES256 or RS256)"
|
|
8635
|
+
})
|
|
8636
|
+
})
|
|
8637
|
+
}).skip(["auth"]).handler(async (c) => {
|
|
8638
|
+
const { body } = await c.data();
|
|
8639
|
+
const result = await oauthStartService(body);
|
|
8640
|
+
return result;
|
|
8641
|
+
});
|
|
8642
|
+
var oauthProviders = route4.get("/_auth/oauth/providers").skip(["auth"]).handler(async () => {
|
|
8643
|
+
return {
|
|
8644
|
+
providers: getEnabledOAuthProviders()
|
|
8645
|
+
};
|
|
8646
|
+
});
|
|
8647
|
+
var getGoogleOAuthUrl = route4.post("/_auth/oauth/google/url").input({
|
|
8648
|
+
body: Type.Object({
|
|
8649
|
+
returnUrl: Type.Optional(Type.String({
|
|
8650
|
+
description: "URL to redirect after OAuth success"
|
|
8651
|
+
})),
|
|
8652
|
+
state: Type.Optional(Type.String({
|
|
8653
|
+
description: "Encrypted OAuth state (injected by interceptor)"
|
|
8654
|
+
}))
|
|
8655
|
+
})
|
|
8656
|
+
}).skip(["auth"]).handler(async (c) => {
|
|
8657
|
+
const { body } = await c.data();
|
|
8658
|
+
if (!isGoogleOAuthEnabled()) {
|
|
8659
|
+
throw new Error("Google OAuth is not configured");
|
|
8660
|
+
}
|
|
8661
|
+
if (!body.state) {
|
|
8662
|
+
throw new Error("OAuth state is required. Ensure the OAuth interceptor is configured.");
|
|
8663
|
+
}
|
|
8664
|
+
return { authUrl: getGoogleAuthUrl(body.state) };
|
|
8665
|
+
});
|
|
8666
|
+
var oauthFinalize = route4.post("/_auth/oauth/finalize").input({
|
|
8667
|
+
body: Type.Object({
|
|
8668
|
+
userId: Type.String({ description: "User ID from OAuth callback" }),
|
|
8669
|
+
keyId: Type.String({ description: "Key ID from OAuth state" }),
|
|
8670
|
+
returnUrl: Type.Optional(Type.String({ description: "URL to redirect after login" }))
|
|
8671
|
+
})
|
|
8672
|
+
}).skip(["auth"]).handler(async (c) => {
|
|
8673
|
+
const { body } = await c.data();
|
|
8674
|
+
return {
|
|
8675
|
+
success: true,
|
|
8676
|
+
returnUrl: body.returnUrl || "/"
|
|
8677
|
+
};
|
|
8678
|
+
});
|
|
8679
|
+
var oauthRouter = defineRouter4({
|
|
8680
|
+
oauthGoogleStart,
|
|
8681
|
+
oauthGoogleCallback,
|
|
8682
|
+
oauthStart,
|
|
8683
|
+
oauthProviders,
|
|
8684
|
+
getGoogleOAuthUrl,
|
|
8685
|
+
oauthFinalize
|
|
8686
|
+
});
|
|
8687
|
+
|
|
8598
8688
|
// src/server/routes/index.ts
|
|
8599
|
-
var mainAuthRouter =
|
|
8600
|
-
//
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8689
|
+
var mainAuthRouter = defineRouter5({
|
|
8690
|
+
// Auth routes
|
|
8691
|
+
checkAccountExists,
|
|
8692
|
+
sendVerificationCode,
|
|
8693
|
+
verifyCode,
|
|
8694
|
+
register,
|
|
8695
|
+
login,
|
|
8696
|
+
logout,
|
|
8697
|
+
rotateKey,
|
|
8698
|
+
changePassword,
|
|
8699
|
+
getAuthSession,
|
|
8700
|
+
// OAuth routes
|
|
8701
|
+
oauthGoogleStart,
|
|
8702
|
+
oauthGoogleCallback,
|
|
8703
|
+
oauthStart,
|
|
8704
|
+
oauthProviders,
|
|
8705
|
+
getGoogleOAuthUrl,
|
|
8706
|
+
oauthFinalize,
|
|
8707
|
+
// Invitation routes
|
|
8708
|
+
getInvitation,
|
|
8709
|
+
acceptInvitation: acceptInvitation2,
|
|
8710
|
+
createInvitation: createInvitation2,
|
|
8711
|
+
listInvitations: listInvitations2,
|
|
8712
|
+
cancelInvitation: cancelInvitation2,
|
|
8713
|
+
resendInvitation: resendInvitation2,
|
|
8714
|
+
deleteInvitation: deleteInvitation2,
|
|
8715
|
+
// User routes
|
|
8716
|
+
getUserProfile,
|
|
8717
|
+
updateUserProfile
|
|
8604
8718
|
});
|
|
8605
8719
|
|
|
8606
8720
|
// src/server.ts
|
|
@@ -8710,11 +8824,11 @@ function shouldRotateKey(createdAt, rotationDays = 90) {
|
|
|
8710
8824
|
}
|
|
8711
8825
|
|
|
8712
8826
|
// src/server/lib/session.ts
|
|
8713
|
-
import * as
|
|
8714
|
-
import { env as
|
|
8827
|
+
import * as jose2 from "jose";
|
|
8828
|
+
import { env as env8 } from "@spfn/auth/config";
|
|
8715
8829
|
import { env as coreEnv } from "@spfn/core/config";
|
|
8716
8830
|
async function getSessionSecretKey() {
|
|
8717
|
-
const secret =
|
|
8831
|
+
const secret = env8.SPFN_AUTH_SESSION_SECRET;
|
|
8718
8832
|
const encoder = new TextEncoder();
|
|
8719
8833
|
const data = encoder.encode(secret);
|
|
8720
8834
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
@@ -8722,24 +8836,24 @@ async function getSessionSecretKey() {
|
|
|
8722
8836
|
}
|
|
8723
8837
|
async function sealSession(data, ttl = 60 * 60 * 24 * 7) {
|
|
8724
8838
|
const secret = await getSessionSecretKey();
|
|
8725
|
-
return await new
|
|
8839
|
+
return await new jose2.EncryptJWT({ data }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${ttl}s`).setIssuer("spfn-auth").setAudience("spfn-client").encrypt(secret);
|
|
8726
8840
|
}
|
|
8727
8841
|
async function unsealSession(jwt4) {
|
|
8728
8842
|
try {
|
|
8729
8843
|
const secret = await getSessionSecretKey();
|
|
8730
|
-
const { payload } = await
|
|
8844
|
+
const { payload } = await jose2.jwtDecrypt(jwt4, secret, {
|
|
8731
8845
|
issuer: "spfn-auth",
|
|
8732
8846
|
audience: "spfn-client"
|
|
8733
8847
|
});
|
|
8734
8848
|
return payload.data;
|
|
8735
8849
|
} catch (err) {
|
|
8736
|
-
if (err instanceof
|
|
8850
|
+
if (err instanceof jose2.errors.JWTExpired) {
|
|
8737
8851
|
throw new Error("Session expired");
|
|
8738
8852
|
}
|
|
8739
|
-
if (err instanceof
|
|
8853
|
+
if (err instanceof jose2.errors.JWEDecryptionFailed) {
|
|
8740
8854
|
throw new Error("Invalid session");
|
|
8741
8855
|
}
|
|
8742
|
-
if (err instanceof
|
|
8856
|
+
if (err instanceof jose2.errors.JWTClaimValidationFailed) {
|
|
8743
8857
|
throw new Error("Session validation failed");
|
|
8744
8858
|
}
|
|
8745
8859
|
throw new Error("Failed to unseal session");
|
|
@@ -8748,7 +8862,7 @@ async function unsealSession(jwt4) {
|
|
|
8748
8862
|
async function getSessionInfo(jwt4) {
|
|
8749
8863
|
const secret = await getSessionSecretKey();
|
|
8750
8864
|
try {
|
|
8751
|
-
const { payload } = await
|
|
8865
|
+
const { payload } = await jose2.jwtDecrypt(jwt4, secret);
|
|
8752
8866
|
return {
|
|
8753
8867
|
issuedAt: new Date(payload.iat * 1e3),
|
|
8754
8868
|
expiresAt: new Date(payload.exp * 1e3),
|
|
@@ -8772,14 +8886,14 @@ async function shouldRefreshSession(jwt4, thresholdHours = 24) {
|
|
|
8772
8886
|
}
|
|
8773
8887
|
|
|
8774
8888
|
// src/server/setup.ts
|
|
8775
|
-
import { env as
|
|
8889
|
+
import { env as env9 } from "@spfn/auth/config";
|
|
8776
8890
|
import { getRoleByName as getRoleByName2 } from "@spfn/auth/server";
|
|
8777
8891
|
init_repositories();
|
|
8778
8892
|
function parseAdminAccounts() {
|
|
8779
8893
|
const accounts = [];
|
|
8780
|
-
if (
|
|
8894
|
+
if (env9.SPFN_AUTH_ADMIN_ACCOUNTS) {
|
|
8781
8895
|
try {
|
|
8782
|
-
const accountsJson =
|
|
8896
|
+
const accountsJson = env9.SPFN_AUTH_ADMIN_ACCOUNTS;
|
|
8783
8897
|
const parsed = JSON.parse(accountsJson);
|
|
8784
8898
|
if (!Array.isArray(parsed)) {
|
|
8785
8899
|
authLogger.setup.error("\u274C SPFN_AUTH_ADMIN_ACCOUNTS must be an array");
|
|
@@ -8806,11 +8920,11 @@ function parseAdminAccounts() {
|
|
|
8806
8920
|
return accounts;
|
|
8807
8921
|
}
|
|
8808
8922
|
}
|
|
8809
|
-
const adminEmails =
|
|
8923
|
+
const adminEmails = env9.SPFN_AUTH_ADMIN_EMAILS;
|
|
8810
8924
|
if (adminEmails) {
|
|
8811
8925
|
const emails = adminEmails.split(",").map((s) => s.trim());
|
|
8812
|
-
const passwords = (
|
|
8813
|
-
const roles2 = (
|
|
8926
|
+
const passwords = (env9.SPFN_AUTH_ADMIN_PASSWORDS || "").split(",").map((s) => s.trim());
|
|
8927
|
+
const roles2 = (env9.SPFN_AUTH_ADMIN_ROLES || "").split(",").map((s) => s.trim());
|
|
8814
8928
|
if (passwords.length !== emails.length) {
|
|
8815
8929
|
authLogger.setup.error("\u274C SPFN_AUTH_ADMIN_EMAILS and SPFN_AUTH_ADMIN_PASSWORDS length mismatch");
|
|
8816
8930
|
return accounts;
|
|
@@ -8832,8 +8946,8 @@ function parseAdminAccounts() {
|
|
|
8832
8946
|
}
|
|
8833
8947
|
return accounts;
|
|
8834
8948
|
}
|
|
8835
|
-
const adminEmail =
|
|
8836
|
-
const adminPassword =
|
|
8949
|
+
const adminEmail = env9.SPFN_AUTH_ADMIN_EMAIL;
|
|
8950
|
+
const adminPassword = env9.SPFN_AUTH_ADMIN_PASSWORD;
|
|
8837
8951
|
if (adminEmail && adminPassword) {
|
|
8838
8952
|
accounts.push({
|
|
8839
8953
|
email: adminEmail,
|
|
@@ -8923,6 +9037,7 @@ export {
|
|
|
8923
9037
|
RolePermissionsRepository,
|
|
8924
9038
|
RolesRepository,
|
|
8925
9039
|
SOCIAL_PROVIDERS,
|
|
9040
|
+
SocialAccountsRepository,
|
|
8926
9041
|
TargetTypeSchema,
|
|
8927
9042
|
USER_STATUSES,
|
|
8928
9043
|
UserPermissionsRepository,
|
|
@@ -8938,16 +9053,19 @@ export {
|
|
|
8938
9053
|
mainAuthRouter as authRouter,
|
|
8939
9054
|
authSchema,
|
|
8940
9055
|
authenticate,
|
|
9056
|
+
buildOAuthErrorUrl,
|
|
8941
9057
|
cancelInvitation,
|
|
8942
9058
|
changePasswordService,
|
|
8943
9059
|
checkAccountExistsService,
|
|
8944
9060
|
configureAuth,
|
|
8945
9061
|
createAuthLifecycle,
|
|
8946
9062
|
createInvitation,
|
|
9063
|
+
createOAuthState,
|
|
8947
9064
|
createRole,
|
|
8948
9065
|
decodeToken,
|
|
8949
9066
|
deleteInvitation,
|
|
8950
9067
|
deleteRole,
|
|
9068
|
+
exchangeCodeForTokens,
|
|
8951
9069
|
expireOldInvitations,
|
|
8952
9070
|
generateClientToken,
|
|
8953
9071
|
generateKeyPair,
|
|
@@ -8958,12 +9076,14 @@ export {
|
|
|
8958
9076
|
getAuth,
|
|
8959
9077
|
getAuthConfig,
|
|
8960
9078
|
getAuthSessionService,
|
|
9079
|
+
getEnabledOAuthProviders,
|
|
9080
|
+
getGoogleAuthUrl,
|
|
9081
|
+
getGoogleOAuthConfig,
|
|
9082
|
+
getGoogleUserInfo,
|
|
8961
9083
|
getInvitationByToken,
|
|
8962
|
-
getInvitationTemplate,
|
|
8963
9084
|
getInvitationWithDetails,
|
|
8964
9085
|
getKeyId,
|
|
8965
9086
|
getKeySize,
|
|
8966
|
-
getPasswordResetTemplate,
|
|
8967
9087
|
getRoleByName,
|
|
8968
9088
|
getRolePermissions,
|
|
8969
9089
|
getSessionInfo,
|
|
@@ -8976,8 +9096,6 @@ export {
|
|
|
8976
9096
|
getUserPermissions,
|
|
8977
9097
|
getUserProfileService,
|
|
8978
9098
|
getUserRole,
|
|
8979
|
-
getVerificationCodeTemplate,
|
|
8980
|
-
getWelcomeTemplate,
|
|
8981
9099
|
hasAllPermissions,
|
|
8982
9100
|
hasAnyPermission,
|
|
8983
9101
|
hasAnyRole,
|
|
@@ -8986,17 +9104,19 @@ export {
|
|
|
8986
9104
|
hashPassword,
|
|
8987
9105
|
initializeAuth,
|
|
8988
9106
|
invitationsRepository,
|
|
9107
|
+
isGoogleOAuthEnabled,
|
|
9108
|
+
isOAuthProviderEnabled,
|
|
8989
9109
|
keysRepository,
|
|
8990
9110
|
listInvitations,
|
|
8991
9111
|
loginService,
|
|
8992
9112
|
logoutService,
|
|
9113
|
+
oauthCallbackService,
|
|
9114
|
+
oauthStartService,
|
|
8993
9115
|
parseDuration,
|
|
8994
9116
|
permissions,
|
|
8995
9117
|
permissionsRepository,
|
|
8996
|
-
|
|
8997
|
-
registerEmailTemplates,
|
|
9118
|
+
refreshAccessToken,
|
|
8998
9119
|
registerPublicKeyService,
|
|
8999
|
-
registerSMSProvider,
|
|
9000
9120
|
registerService,
|
|
9001
9121
|
removePermissionFromRole,
|
|
9002
9122
|
requireAnyPermission,
|
|
@@ -9011,12 +9131,11 @@ export {
|
|
|
9011
9131
|
rolesRepository,
|
|
9012
9132
|
rotateKeyService,
|
|
9013
9133
|
sealSession,
|
|
9014
|
-
sendEmail,
|
|
9015
|
-
sendSMS,
|
|
9016
9134
|
sendVerificationCodeService,
|
|
9017
9135
|
setRolePermissions,
|
|
9018
9136
|
shouldRefreshSession,
|
|
9019
9137
|
shouldRotateKey,
|
|
9138
|
+
socialAccountsRepository,
|
|
9020
9139
|
unsealSession,
|
|
9021
9140
|
updateLastLoginService,
|
|
9022
9141
|
updateRole,
|
|
@@ -9038,6 +9157,7 @@ export {
|
|
|
9038
9157
|
verifyClientToken,
|
|
9039
9158
|
verifyCodeService,
|
|
9040
9159
|
verifyKeyFingerprint,
|
|
9160
|
+
verifyOAuthState,
|
|
9041
9161
|
verifyPassword,
|
|
9042
9162
|
verifyToken
|
|
9043
9163
|
};
|