@spfn/auth 0.3.0-beta.5 → 0.3.0-beta.6
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 +82 -0
- package/dist/{authenticate-Ctul07Sc.d.ts → authenticate-98lBIMxP.d.ts} +115 -1
- package/dist/config.d.ts +60 -0
- package/dist/config.js +27 -0
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +39 -2
- package/dist/errors.js +24 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +29 -2
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -1
- package/dist/nextjs/api.js +58 -1
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/server.js +4 -0
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +281 -7
- package/dist/server.js +782 -367
- package/dist/server.js.map +1 -1
- package/migrations/20260810112144_colorful_tomorrow_man/migration.sql +18 -0
- package/migrations/20260810112144_colorful_tomorrow_man/snapshot.json +3576 -0
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -897,8 +897,8 @@ var init_array2 = __esm({
|
|
|
897
897
|
});
|
|
898
898
|
|
|
899
899
|
// ../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/argument/argument.mjs
|
|
900
|
-
function Argument(
|
|
901
|
-
return CreateType({ [Kind]: "Argument", index:
|
|
900
|
+
function Argument(index13) {
|
|
901
|
+
return CreateType({ [Kind]: "Argument", index: index13 });
|
|
902
902
|
}
|
|
903
903
|
var init_argument = __esm({
|
|
904
904
|
"../../node_modules/.pnpm/@sinclair+typebox@0.34.41/node_modules/@sinclair/typebox/build/esm/type/argument/argument.mjs"() {
|
|
@@ -1136,28 +1136,28 @@ var init_union2 = __esm({
|
|
|
1136
1136
|
function Unescape(pattern) {
|
|
1137
1137
|
return pattern.replace(/\\\$/g, "$").replace(/\\\*/g, "*").replace(/\\\^/g, "^").replace(/\\\|/g, "|").replace(/\\\(/g, "(").replace(/\\\)/g, ")");
|
|
1138
1138
|
}
|
|
1139
|
-
function IsNonEscaped(pattern,
|
|
1140
|
-
return pattern[
|
|
1139
|
+
function IsNonEscaped(pattern, index13, char) {
|
|
1140
|
+
return pattern[index13] === char && pattern.charCodeAt(index13 - 1) !== 92;
|
|
1141
1141
|
}
|
|
1142
|
-
function IsOpenParen(pattern,
|
|
1143
|
-
return IsNonEscaped(pattern,
|
|
1142
|
+
function IsOpenParen(pattern, index13) {
|
|
1143
|
+
return IsNonEscaped(pattern, index13, "(");
|
|
1144
1144
|
}
|
|
1145
|
-
function IsCloseParen(pattern,
|
|
1146
|
-
return IsNonEscaped(pattern,
|
|
1145
|
+
function IsCloseParen(pattern, index13) {
|
|
1146
|
+
return IsNonEscaped(pattern, index13, ")");
|
|
1147
1147
|
}
|
|
1148
|
-
function IsSeparator(pattern,
|
|
1149
|
-
return IsNonEscaped(pattern,
|
|
1148
|
+
function IsSeparator(pattern, index13) {
|
|
1149
|
+
return IsNonEscaped(pattern, index13, "|");
|
|
1150
1150
|
}
|
|
1151
1151
|
function IsGroup(pattern) {
|
|
1152
1152
|
if (!(IsOpenParen(pattern, 0) && IsCloseParen(pattern, pattern.length - 1)))
|
|
1153
1153
|
return false;
|
|
1154
1154
|
let count = 0;
|
|
1155
|
-
for (let
|
|
1156
|
-
if (IsOpenParen(pattern,
|
|
1155
|
+
for (let index13 = 0; index13 < pattern.length; index13++) {
|
|
1156
|
+
if (IsOpenParen(pattern, index13))
|
|
1157
1157
|
count += 1;
|
|
1158
|
-
if (IsCloseParen(pattern,
|
|
1158
|
+
if (IsCloseParen(pattern, index13))
|
|
1159
1159
|
count -= 1;
|
|
1160
|
-
if (count === 0 &&
|
|
1160
|
+
if (count === 0 && index13 !== pattern.length - 1)
|
|
1161
1161
|
return false;
|
|
1162
1162
|
}
|
|
1163
1163
|
return true;
|
|
@@ -1167,19 +1167,19 @@ function InGroup(pattern) {
|
|
|
1167
1167
|
}
|
|
1168
1168
|
function IsPrecedenceOr(pattern) {
|
|
1169
1169
|
let count = 0;
|
|
1170
|
-
for (let
|
|
1171
|
-
if (IsOpenParen(pattern,
|
|
1170
|
+
for (let index13 = 0; index13 < pattern.length; index13++) {
|
|
1171
|
+
if (IsOpenParen(pattern, index13))
|
|
1172
1172
|
count += 1;
|
|
1173
|
-
if (IsCloseParen(pattern,
|
|
1173
|
+
if (IsCloseParen(pattern, index13))
|
|
1174
1174
|
count -= 1;
|
|
1175
|
-
if (IsSeparator(pattern,
|
|
1175
|
+
if (IsSeparator(pattern, index13) && count === 0)
|
|
1176
1176
|
return true;
|
|
1177
1177
|
}
|
|
1178
1178
|
return false;
|
|
1179
1179
|
}
|
|
1180
1180
|
function IsPrecedenceAnd(pattern) {
|
|
1181
|
-
for (let
|
|
1182
|
-
if (IsOpenParen(pattern,
|
|
1181
|
+
for (let index13 = 0; index13 < pattern.length; index13++) {
|
|
1182
|
+
if (IsOpenParen(pattern, index13))
|
|
1183
1183
|
return true;
|
|
1184
1184
|
}
|
|
1185
1185
|
return false;
|
|
@@ -1187,16 +1187,16 @@ function IsPrecedenceAnd(pattern) {
|
|
|
1187
1187
|
function Or(pattern) {
|
|
1188
1188
|
let [count, start] = [0, 0];
|
|
1189
1189
|
const expressions = [];
|
|
1190
|
-
for (let
|
|
1191
|
-
if (IsOpenParen(pattern,
|
|
1190
|
+
for (let index13 = 0; index13 < pattern.length; index13++) {
|
|
1191
|
+
if (IsOpenParen(pattern, index13))
|
|
1192
1192
|
count += 1;
|
|
1193
|
-
if (IsCloseParen(pattern,
|
|
1193
|
+
if (IsCloseParen(pattern, index13))
|
|
1194
1194
|
count -= 1;
|
|
1195
|
-
if (IsSeparator(pattern,
|
|
1196
|
-
const range2 = pattern.slice(start,
|
|
1195
|
+
if (IsSeparator(pattern, index13) && count === 0) {
|
|
1196
|
+
const range2 = pattern.slice(start, index13);
|
|
1197
1197
|
if (range2.length > 0)
|
|
1198
1198
|
expressions.push(TemplateLiteralParse(range2));
|
|
1199
|
-
start =
|
|
1199
|
+
start = index13 + 1;
|
|
1200
1200
|
}
|
|
1201
1201
|
}
|
|
1202
1202
|
const range = pattern.slice(start);
|
|
@@ -1209,40 +1209,40 @@ function Or(pattern) {
|
|
|
1209
1209
|
return { type: "or", expr: expressions };
|
|
1210
1210
|
}
|
|
1211
1211
|
function And(pattern) {
|
|
1212
|
-
function Group(value,
|
|
1213
|
-
if (!IsOpenParen(value,
|
|
1212
|
+
function Group(value, index13) {
|
|
1213
|
+
if (!IsOpenParen(value, index13))
|
|
1214
1214
|
throw new TemplateLiteralParserError(`TemplateLiteralParser: Index must point to open parens`);
|
|
1215
1215
|
let count = 0;
|
|
1216
|
-
for (let scan =
|
|
1216
|
+
for (let scan = index13; scan < value.length; scan++) {
|
|
1217
1217
|
if (IsOpenParen(value, scan))
|
|
1218
1218
|
count += 1;
|
|
1219
1219
|
if (IsCloseParen(value, scan))
|
|
1220
1220
|
count -= 1;
|
|
1221
1221
|
if (count === 0)
|
|
1222
|
-
return [
|
|
1222
|
+
return [index13, scan];
|
|
1223
1223
|
}
|
|
1224
1224
|
throw new TemplateLiteralParserError(`TemplateLiteralParser: Unclosed group parens in expression`);
|
|
1225
1225
|
}
|
|
1226
|
-
function Range(pattern2,
|
|
1227
|
-
for (let scan =
|
|
1226
|
+
function Range(pattern2, index13) {
|
|
1227
|
+
for (let scan = index13; scan < pattern2.length; scan++) {
|
|
1228
1228
|
if (IsOpenParen(pattern2, scan))
|
|
1229
|
-
return [
|
|
1229
|
+
return [index13, scan];
|
|
1230
1230
|
}
|
|
1231
|
-
return [
|
|
1231
|
+
return [index13, pattern2.length];
|
|
1232
1232
|
}
|
|
1233
1233
|
const expressions = [];
|
|
1234
|
-
for (let
|
|
1235
|
-
if (IsOpenParen(pattern,
|
|
1236
|
-
const [start, end] = Group(pattern,
|
|
1234
|
+
for (let index13 = 0; index13 < pattern.length; index13++) {
|
|
1235
|
+
if (IsOpenParen(pattern, index13)) {
|
|
1236
|
+
const [start, end] = Group(pattern, index13);
|
|
1237
1237
|
const range = pattern.slice(start, end + 1);
|
|
1238
1238
|
expressions.push(TemplateLiteralParse(range));
|
|
1239
|
-
|
|
1239
|
+
index13 = end;
|
|
1240
1240
|
} else {
|
|
1241
|
-
const [start, end] = Range(pattern,
|
|
1241
|
+
const [start, end] = Range(pattern, index13);
|
|
1242
1242
|
const range = pattern.slice(start, end);
|
|
1243
1243
|
if (range.length > 0)
|
|
1244
1244
|
expressions.push(TemplateLiteralParse(range));
|
|
1245
|
-
|
|
1245
|
+
index13 = end - 1;
|
|
1246
1246
|
}
|
|
1247
1247
|
}
|
|
1248
1248
|
return expressions.length === 0 ? { type: "const", const: "" } : expressions.length === 1 ? expressions[0] : { type: "and", expr: expressions };
|
|
@@ -2577,13 +2577,13 @@ function FromBoolean(left, right) {
|
|
|
2577
2577
|
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) ? FromObjectRight(left, right) : type_exports.IsRecord(right) ? FromRecordRight(left, right) : type_exports.IsBoolean(right) ? ExtendsResult.True : ExtendsResult.False;
|
|
2578
2578
|
}
|
|
2579
2579
|
function FromConstructor(left, right) {
|
|
2580
|
-
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) ? FromObjectRight(left, right) : !type_exports.IsConstructor(right) ? ExtendsResult.False : left.parameters.length > right.parameters.length ? ExtendsResult.False : !left.parameters.every((schema,
|
|
2580
|
+
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) ? FromObjectRight(left, right) : !type_exports.IsConstructor(right) ? ExtendsResult.False : left.parameters.length > right.parameters.length ? ExtendsResult.False : !left.parameters.every((schema, index13) => IntoBooleanResult(Visit3(right.parameters[index13], schema)) === ExtendsResult.True) ? ExtendsResult.False : IntoBooleanResult(Visit3(left.returns, right.returns));
|
|
2581
2581
|
}
|
|
2582
2582
|
function FromDate(left, right) {
|
|
2583
2583
|
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) ? FromObjectRight(left, right) : type_exports.IsRecord(right) ? FromRecordRight(left, right) : type_exports.IsDate(right) ? ExtendsResult.True : ExtendsResult.False;
|
|
2584
2584
|
}
|
|
2585
2585
|
function FromFunction(left, right) {
|
|
2586
|
-
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) ? FromObjectRight(left, right) : !type_exports.IsFunction(right) ? ExtendsResult.False : left.parameters.length > right.parameters.length ? ExtendsResult.False : !left.parameters.every((schema,
|
|
2586
|
+
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) ? FromObjectRight(left, right) : !type_exports.IsFunction(right) ? ExtendsResult.False : left.parameters.length > right.parameters.length ? ExtendsResult.False : !left.parameters.every((schema, index13) => IntoBooleanResult(Visit3(right.parameters[index13], schema)) === ExtendsResult.True) ? ExtendsResult.False : IntoBooleanResult(Visit3(left.returns, right.returns));
|
|
2587
2587
|
}
|
|
2588
2588
|
function FromIntegerRight(left, right) {
|
|
2589
2589
|
return type_exports.IsLiteral(left) && value_exports.IsNumber(left.const) ? ExtendsResult.True : type_exports.IsNumber(left) || type_exports.IsInteger(left) ? ExtendsResult.True : ExtendsResult.False;
|
|
@@ -2743,7 +2743,7 @@ function FromTupleRight(left, right) {
|
|
|
2743
2743
|
return type_exports.IsNever(left) ? ExtendsResult.True : type_exports.IsUnknown(left) ? ExtendsResult.False : type_exports.IsAny(left) ? ExtendsResult.Union : ExtendsResult.False;
|
|
2744
2744
|
}
|
|
2745
2745
|
function FromTuple3(left, right) {
|
|
2746
|
-
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) && IsObjectArrayLike(right) ? ExtendsResult.True : type_exports.IsArray(right) && IsArrayOfTuple(left, right) ? ExtendsResult.True : !type_exports.IsTuple(right) ? ExtendsResult.False : value_exports.IsUndefined(left.items) && !value_exports.IsUndefined(right.items) || !value_exports.IsUndefined(left.items) && value_exports.IsUndefined(right.items) ? ExtendsResult.False : value_exports.IsUndefined(left.items) && !value_exports.IsUndefined(right.items) ? ExtendsResult.True : left.items.every((schema,
|
|
2746
|
+
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) && IsObjectArrayLike(right) ? ExtendsResult.True : type_exports.IsArray(right) && IsArrayOfTuple(left, right) ? ExtendsResult.True : !type_exports.IsTuple(right) ? ExtendsResult.False : value_exports.IsUndefined(left.items) && !value_exports.IsUndefined(right.items) || !value_exports.IsUndefined(left.items) && value_exports.IsUndefined(right.items) ? ExtendsResult.False : value_exports.IsUndefined(left.items) && !value_exports.IsUndefined(right.items) ? ExtendsResult.True : left.items.every((schema, index13) => Visit3(schema, right.items[index13]) === ExtendsResult.True) ? ExtendsResult.True : ExtendsResult.False;
|
|
2747
2747
|
}
|
|
2748
2748
|
function FromUint8Array(left, right) {
|
|
2749
2749
|
return IsStructuralRight(right) ? StructuralRight(left, right) : type_exports.IsObject(right) ? FromObjectRight(left, right) : type_exports.IsRecord(right) ? FromRecordRight(left, right) : type_exports.IsUint8Array(right) ? ExtendsResult.True : ExtendsResult.False;
|
|
@@ -4978,9 +4978,59 @@ var init_verification_codes = __esm({
|
|
|
4978
4978
|
}
|
|
4979
4979
|
});
|
|
4980
4980
|
|
|
4981
|
+
// src/server/entities/signup-link-tokens.ts
|
|
4982
|
+
import { text as text7, index as index7, uniqueIndex as uniqueIndex2 } from "drizzle-orm/pg-core";
|
|
4983
|
+
import { id as id7, timestamps as timestamps6, utcTimestamp as utcTimestamp5 } from "@spfn/core/db";
|
|
4984
|
+
var signupLinkTokens;
|
|
4985
|
+
var init_signup_link_tokens = __esm({
|
|
4986
|
+
"src/server/entities/signup-link-tokens.ts"() {
|
|
4987
|
+
"use strict";
|
|
4988
|
+
init_schema4();
|
|
4989
|
+
signupLinkTokens = authSchema.table(
|
|
4990
|
+
"signup_link_tokens",
|
|
4991
|
+
{
|
|
4992
|
+
id: id7(),
|
|
4993
|
+
// Normalized email address the link was issued for
|
|
4994
|
+
email: text7("email").notNull(),
|
|
4995
|
+
// SHA-256 of the emailed token, base64url
|
|
4996
|
+
// The token itself (32 random bytes) is never stored
|
|
4997
|
+
tokenHash: text7("token_hash").notNull(),
|
|
4998
|
+
// Relative path to return the user to after signup completes
|
|
4999
|
+
// Validated as a relative path on the way in; never an absolute URL
|
|
5000
|
+
returnPath: text7("return_path"),
|
|
5001
|
+
// Link expiry — SPFN_AUTH_SIGNUP_LINK_TTL_MINUTES from creation
|
|
5002
|
+
expiresAt: utcTimestamp5("expires_at").notNull(),
|
|
5003
|
+
// Set when the link is exchanged for a setup session
|
|
5004
|
+
// Non-null means the link is spent; it is one-time regardless of expiry
|
|
5005
|
+
consumedAt: utcTimestamp5("consumed_at"),
|
|
5006
|
+
// Set when a newer link for the same email replaced this one
|
|
5007
|
+
// A superseded row is refused even if it has not expired or been consumed
|
|
5008
|
+
supersededAt: utcTimestamp5("superseded_at"),
|
|
5009
|
+
// SHA-256 of the setup session secret, written at consume time
|
|
5010
|
+
// The secret lives only in the caller's HttpOnly cookie
|
|
5011
|
+
setupSecretHash: text7("setup_secret_hash"),
|
|
5012
|
+
// Setup session expiry — SPFN_AUTH_SIGNUP_SETUP_TTL_MINUTES from consume
|
|
5013
|
+
setupExpiresAt: utcTimestamp5("setup_expires_at"),
|
|
5014
|
+
// Terminal: the password was set and the account created
|
|
5015
|
+
completedAt: utcTimestamp5("completed_at"),
|
|
5016
|
+
...timestamps6()
|
|
5017
|
+
},
|
|
5018
|
+
(table) => [
|
|
5019
|
+
// Lookup path for confirming a link
|
|
5020
|
+
uniqueIndex2("signup_link_token_hash_idx").on(table.tokenHash),
|
|
5021
|
+
// Lookup path for setting a password
|
|
5022
|
+
// Unique so a setup secret can never address two rows
|
|
5023
|
+
uniqueIndex2("signup_link_setup_secret_hash_idx").on(table.setupSecretHash),
|
|
5024
|
+
// Supersede-on-resend scans the live rows for one address
|
|
5025
|
+
index7("signup_link_email_idx").on(table.email, table.expiresAt)
|
|
5026
|
+
]
|
|
5027
|
+
);
|
|
5028
|
+
}
|
|
5029
|
+
});
|
|
5030
|
+
|
|
4981
5031
|
// src/server/entities/user-invitations.ts
|
|
4982
|
-
import { text as
|
|
4983
|
-
import { id as
|
|
5032
|
+
import { text as text8, index as index8 } from "drizzle-orm/pg-core";
|
|
5033
|
+
import { id as id8, timestamps as timestamps7, enumText as enumText5, utcTimestamp as utcTimestamp6, typedJsonb as typedJsonb2, foreignKey as foreignKey5 } from "@spfn/core/db";
|
|
4984
5034
|
var userInvitations;
|
|
4985
5035
|
var init_user_invitations = __esm({
|
|
4986
5036
|
"src/server/entities/user-invitations.ts"() {
|
|
@@ -4993,14 +5043,14 @@ var init_user_invitations = __esm({
|
|
|
4993
5043
|
"user_invitations",
|
|
4994
5044
|
{
|
|
4995
5045
|
// Primary key
|
|
4996
|
-
id:
|
|
5046
|
+
id: id8(),
|
|
4997
5047
|
// Target email address for the invitation
|
|
4998
5048
|
// Will become the user's email upon acceptance
|
|
4999
|
-
email:
|
|
5049
|
+
email: text8("email").notNull(),
|
|
5000
5050
|
// Unique invitation token (UUID v4)
|
|
5001
5051
|
// Used in invitation URL: /auth/invite/{token}
|
|
5002
5052
|
// Single-use token that expires after acceptance
|
|
5003
|
-
token:
|
|
5053
|
+
token: text8("token").notNull().unique(),
|
|
5004
5054
|
// Role to be assigned when invitation is accepted
|
|
5005
5055
|
// Foreign key to roles table
|
|
5006
5056
|
roleId: foreignKey5("role", () => roles.id),
|
|
@@ -5017,15 +5067,15 @@ var init_user_invitations = __esm({
|
|
|
5017
5067
|
// Expiration timestamp (default: 7 days from creation)
|
|
5018
5068
|
// Invitation cannot be accepted after this time
|
|
5019
5069
|
// Background job should update status to 'expired'
|
|
5020
|
-
expiresAt:
|
|
5070
|
+
expiresAt: utcTimestamp6("expires_at").notNull(),
|
|
5021
5071
|
// Timestamp when invitation was accepted
|
|
5022
5072
|
// null = not yet accepted
|
|
5023
5073
|
// Used for: audit trail, analytics
|
|
5024
|
-
acceptedAt:
|
|
5074
|
+
acceptedAt: utcTimestamp6("accepted_at"),
|
|
5025
5075
|
// Timestamp when invitation was cancelled
|
|
5026
5076
|
// null = not cancelled
|
|
5027
5077
|
// Used for: audit trail
|
|
5028
|
-
cancelledAt:
|
|
5078
|
+
cancelledAt: utcTimestamp6("cancelled_at"),
|
|
5029
5079
|
// Additional metadata (JSONB)
|
|
5030
5080
|
// Use cases:
|
|
5031
5081
|
// - Custom welcome message
|
|
@@ -5034,26 +5084,26 @@ var init_user_invitations = __esm({
|
|
|
5034
5084
|
// - Custom fields for app-specific data
|
|
5035
5085
|
// Example: { message: "Welcome!", department: "Engineering" }
|
|
5036
5086
|
metadata: typedJsonb2("metadata"),
|
|
5037
|
-
...
|
|
5087
|
+
...timestamps7()
|
|
5038
5088
|
},
|
|
5039
5089
|
(table) => [
|
|
5040
5090
|
// Indexes for query optimization
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5091
|
+
index8("invitations_token_idx").on(table.token),
|
|
5092
|
+
index8("invitations_email_idx").on(table.email),
|
|
5093
|
+
index8("invitations_status_idx").on(table.status),
|
|
5094
|
+
index8("invitations_invited_by_idx").on(table.invitedBy),
|
|
5095
|
+
index8("invitations_expires_at_idx").on(table.expiresAt),
|
|
5046
5096
|
// For cleanup jobs
|
|
5047
|
-
|
|
5097
|
+
index8("invitations_role_id_idx").on(table.roleId)
|
|
5048
5098
|
]
|
|
5049
5099
|
);
|
|
5050
5100
|
}
|
|
5051
5101
|
});
|
|
5052
5102
|
|
|
5053
5103
|
// src/server/entities/account-deletion-requests.ts
|
|
5054
|
-
import { text as
|
|
5104
|
+
import { text as text9, index as index9, uniqueIndex as uniqueIndex3 } from "drizzle-orm/pg-core";
|
|
5055
5105
|
import { sql as sql2 } from "drizzle-orm";
|
|
5056
|
-
import { id as
|
|
5106
|
+
import { id as id9, timestamps as timestamps8, enumText as enumText6, utcTimestamp as utcTimestamp7, optionalForeignKey } from "@spfn/core/db";
|
|
5057
5107
|
var accountDeletionRequests;
|
|
5058
5108
|
var init_account_deletion_requests = __esm({
|
|
5059
5109
|
"src/server/entities/account-deletion-requests.ts"() {
|
|
@@ -5064,18 +5114,18 @@ var init_account_deletion_requests = __esm({
|
|
|
5064
5114
|
accountDeletionRequests = authSchema.table(
|
|
5065
5115
|
"account_deletion_requests",
|
|
5066
5116
|
{
|
|
5067
|
-
id:
|
|
5117
|
+
id: id9(),
|
|
5068
5118
|
// Foreign key to users table. `set null` (optionalForeignKey default) so this
|
|
5069
5119
|
// row survives a hard-delete purge of the user it refers to.
|
|
5070
5120
|
userId: optionalForeignKey("user", () => users.id),
|
|
5071
5121
|
// Snapshot of the user's public UUID at request time — stays readable even
|
|
5072
5122
|
// after userId is nulled out or the account is anonymized.
|
|
5073
|
-
userPublicId:
|
|
5123
|
+
userPublicId: text9("user_public_id").notNull(),
|
|
5074
5124
|
// When the deletion was requested
|
|
5075
|
-
requestedAt:
|
|
5125
|
+
requestedAt: utcTimestamp7("requested_at").notNull().defaultNow(),
|
|
5076
5126
|
// When the purge job is allowed to run (requestedAt + grace period; equals
|
|
5077
5127
|
// requestedAt itself for immediate/zero-grace deletions)
|
|
5078
|
-
purgeScheduledAt:
|
|
5128
|
+
purgeScheduledAt: utcTimestamp7("purge_scheduled_at").notNull(),
|
|
5079
5129
|
// Request lifecycle status
|
|
5080
5130
|
// - pending: awaiting purgeScheduledAt (or immediate purge)
|
|
5081
5131
|
// - cancelled: recovered before purge
|
|
@@ -5084,20 +5134,20 @@ var init_account_deletion_requests = __esm({
|
|
|
5084
5134
|
// Who initiated the request
|
|
5085
5135
|
requestedBy: enumText6("requested_by", ACCOUNT_DELETION_REQUESTED_BY).default("self").notNull(),
|
|
5086
5136
|
// Optional free-text reason (self-service UI, admin note, DSR reference, ...)
|
|
5087
|
-
reason:
|
|
5088
|
-
cancelledAt:
|
|
5089
|
-
completedAt:
|
|
5137
|
+
reason: text9("reason"),
|
|
5138
|
+
cancelledAt: utcTimestamp7("cancelled_at"),
|
|
5139
|
+
completedAt: utcTimestamp7("completed_at"),
|
|
5090
5140
|
// Purge strategy actually executed (set on completion; null while pending)
|
|
5091
5141
|
purgeStrategy: enumText6("purge_strategy", PURGE_STRATEGIES),
|
|
5092
|
-
...
|
|
5142
|
+
...timestamps8()
|
|
5093
5143
|
},
|
|
5094
5144
|
(table) => [
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5145
|
+
index9("account_deletion_requests_user_id_idx").on(table.userId),
|
|
5146
|
+
index9("account_deletion_requests_status_idx").on(table.status),
|
|
5147
|
+
index9("account_deletion_requests_purge_scheduled_at_idx").on(table.purgeScheduledAt),
|
|
5148
|
+
index9("account_deletion_requests_user_public_id_idx").on(table.userPublicId),
|
|
5099
5149
|
// Partial unique index: at most one pending request per user at a time.
|
|
5100
|
-
|
|
5150
|
+
uniqueIndex3("account_deletion_requests_user_pending_unique_idx").on(table.userId).where(sql2`${table.status} = 'pending'`)
|
|
5101
5151
|
]
|
|
5102
5152
|
);
|
|
5103
5153
|
}
|
|
@@ -5249,8 +5299,8 @@ var init_rbac = __esm({
|
|
|
5249
5299
|
});
|
|
5250
5300
|
|
|
5251
5301
|
// src/server/entities/permissions.ts
|
|
5252
|
-
import { text as
|
|
5253
|
-
import { id as
|
|
5302
|
+
import { text as text10, boolean as boolean4, index as index10 } from "drizzle-orm/pg-core";
|
|
5303
|
+
import { id as id10, timestamps as timestamps9, enumText as enumText7, typedJsonb as typedJsonb3 } from "@spfn/core/db";
|
|
5254
5304
|
var permissions;
|
|
5255
5305
|
var init_permissions = __esm({
|
|
5256
5306
|
"src/server/entities/permissions.ts"() {
|
|
@@ -5261,7 +5311,7 @@ var init_permissions = __esm({
|
|
|
5261
5311
|
"permissions",
|
|
5262
5312
|
{
|
|
5263
5313
|
// Primary key
|
|
5264
|
-
id:
|
|
5314
|
+
id: id10(),
|
|
5265
5315
|
// Permission identifier
|
|
5266
5316
|
// Format: resource:action or namespace:resource:action
|
|
5267
5317
|
// Examples:
|
|
@@ -5269,15 +5319,15 @@ var init_permissions = __esm({
|
|
|
5269
5319
|
// - Namespaced: 'auth:user:delete', 'cms:post:publish'
|
|
5270
5320
|
// Must be unique across all permissions
|
|
5271
5321
|
// Used in: permission checks, role assignments, API guards
|
|
5272
|
-
name:
|
|
5322
|
+
name: text10("name").notNull().unique(),
|
|
5273
5323
|
// Display name for UI
|
|
5274
5324
|
// Human-readable name shown in admin panels
|
|
5275
5325
|
// Example: "Delete Users", "Publish Posts"
|
|
5276
|
-
displayName:
|
|
5326
|
+
displayName: text10("display_name").notNull(),
|
|
5277
5327
|
// Permission description
|
|
5278
5328
|
// Detailed explanation of what this permission allows
|
|
5279
5329
|
// Example: "Allows deletion of user accounts from the system"
|
|
5280
|
-
description:
|
|
5330
|
+
description: text10("description"),
|
|
5281
5331
|
// Category for grouping
|
|
5282
5332
|
// Used for: organizing permissions in UI, filtering
|
|
5283
5333
|
// Built-in categories: auth, user, rbac, system
|
|
@@ -5313,22 +5363,22 @@ var init_permissions = __esm({
|
|
|
5313
5363
|
// - Audit: { createdBy: 123, source: 'migration', version: '1.0.0' }
|
|
5314
5364
|
// Example: { icon: 'trash', color: 'red', requiresMfa: true }
|
|
5315
5365
|
metadata: typedJsonb3("metadata"),
|
|
5316
|
-
...
|
|
5366
|
+
...timestamps9()
|
|
5317
5367
|
},
|
|
5318
5368
|
(table) => [
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5369
|
+
index10("permissions_name_idx").on(table.name),
|
|
5370
|
+
index10("permissions_category_idx").on(table.category),
|
|
5371
|
+
index10("permissions_is_system_idx").on(table.isSystem),
|
|
5372
|
+
index10("permissions_is_active_idx").on(table.isActive),
|
|
5373
|
+
index10("permissions_is_builtin_idx").on(table.isBuiltin)
|
|
5324
5374
|
]
|
|
5325
5375
|
);
|
|
5326
5376
|
}
|
|
5327
5377
|
});
|
|
5328
5378
|
|
|
5329
5379
|
// src/server/entities/role-permissions.ts
|
|
5330
|
-
import { index as
|
|
5331
|
-
import { id as
|
|
5380
|
+
import { index as index11, unique } from "drizzle-orm/pg-core";
|
|
5381
|
+
import { id as id11, timestamps as timestamps10, foreignKey as foreignKey6 } from "@spfn/core/db";
|
|
5332
5382
|
var rolePermissions;
|
|
5333
5383
|
var init_role_permissions = __esm({
|
|
5334
5384
|
"src/server/entities/role-permissions.ts"() {
|
|
@@ -5340,7 +5390,7 @@ var init_role_permissions = __esm({
|
|
|
5340
5390
|
"role_permissions",
|
|
5341
5391
|
{
|
|
5342
5392
|
// Primary key
|
|
5343
|
-
id:
|
|
5393
|
+
id: id11(),
|
|
5344
5394
|
// Role reference
|
|
5345
5395
|
// Foreign key to roles table
|
|
5346
5396
|
// Cascade delete: when role is deleted, all role-permission mappings are removed
|
|
@@ -5353,12 +5403,12 @@ var init_role_permissions = __esm({
|
|
|
5353
5403
|
// Used for: granting permissions to roles
|
|
5354
5404
|
// Example: user:delete permission → [Admin, Superadmin]
|
|
5355
5405
|
permissionId: foreignKey6("permission", () => permissions.id, { onDelete: "cascade" }),
|
|
5356
|
-
...
|
|
5406
|
+
...timestamps10()
|
|
5357
5407
|
},
|
|
5358
5408
|
(table) => [
|
|
5359
5409
|
// Indexes for query performance
|
|
5360
|
-
|
|
5361
|
-
|
|
5410
|
+
index11("role_permissions_role_id_idx").on(table.roleId),
|
|
5411
|
+
index11("role_permissions_permission_id_idx").on(table.permissionId),
|
|
5362
5412
|
// Unique constraint: one role-permission pair only
|
|
5363
5413
|
unique("role_permissions_unique").on(table.roleId, table.permissionId)
|
|
5364
5414
|
]
|
|
@@ -5367,8 +5417,8 @@ var init_role_permissions = __esm({
|
|
|
5367
5417
|
});
|
|
5368
5418
|
|
|
5369
5419
|
// src/server/entities/user-permissions.ts
|
|
5370
|
-
import { boolean as boolean5, text as
|
|
5371
|
-
import { id as
|
|
5420
|
+
import { boolean as boolean5, text as text11, index as index12, unique as unique2 } from "drizzle-orm/pg-core";
|
|
5421
|
+
import { id as id12, timestamps as timestamps11, utcTimestamp as utcTimestamp8, foreignKey as foreignKey7 } from "@spfn/core/db";
|
|
5372
5422
|
var userPermissions;
|
|
5373
5423
|
var init_user_permissions = __esm({
|
|
5374
5424
|
"src/server/entities/user-permissions.ts"() {
|
|
@@ -5380,7 +5430,7 @@ var init_user_permissions = __esm({
|
|
|
5380
5430
|
"user_permissions",
|
|
5381
5431
|
{
|
|
5382
5432
|
// Primary key
|
|
5383
|
-
id:
|
|
5433
|
+
id: id12(),
|
|
5384
5434
|
// User reference
|
|
5385
5435
|
// Foreign key to users table
|
|
5386
5436
|
// Cascade delete: when user is deleted, all overrides are removed
|
|
@@ -5403,19 +5453,19 @@ var init_user_permissions = __esm({
|
|
|
5403
5453
|
// Reason for grant/revocation
|
|
5404
5454
|
// Used for: audit trail, compliance documentation
|
|
5405
5455
|
// Example: "Temporary access for project X", "Security incident - restricted"
|
|
5406
|
-
reason:
|
|
5456
|
+
reason: text11("reason"),
|
|
5407
5457
|
// Expiration timestamp (optional)
|
|
5408
5458
|
// null: Permanent override (remains until manually removed)
|
|
5409
5459
|
// timestamp: Permission expires at this time (auto-revoked by background job)
|
|
5410
5460
|
// Use case: Time-limited elevated access, temporary restrictions
|
|
5411
|
-
expiresAt:
|
|
5412
|
-
...
|
|
5461
|
+
expiresAt: utcTimestamp8("expires_at"),
|
|
5462
|
+
...timestamps11()
|
|
5413
5463
|
},
|
|
5414
5464
|
(table) => [
|
|
5415
5465
|
// Indexes for query performance
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5466
|
+
index12("user_permissions_user_id_idx").on(table.userId),
|
|
5467
|
+
index12("user_permissions_permission_id_idx").on(table.permissionId),
|
|
5468
|
+
index12("user_permissions_expires_at_idx").on(table.expiresAt),
|
|
5419
5469
|
// Unique constraint: one user-permission pair only
|
|
5420
5470
|
unique2("user_permissions_unique").on(table.userId, table.permissionId)
|
|
5421
5471
|
]
|
|
@@ -5425,7 +5475,7 @@ var init_user_permissions = __esm({
|
|
|
5425
5475
|
|
|
5426
5476
|
// src/server/entities/auth-metadata.ts
|
|
5427
5477
|
import { sql as sql3 } from "drizzle-orm";
|
|
5428
|
-
import { text as
|
|
5478
|
+
import { text as text12, timestamp } from "drizzle-orm/pg-core";
|
|
5429
5479
|
var authMetadata;
|
|
5430
5480
|
var init_auth_metadata = __esm({
|
|
5431
5481
|
"src/server/entities/auth-metadata.ts"() {
|
|
@@ -5435,9 +5485,9 @@ var init_auth_metadata = __esm({
|
|
|
5435
5485
|
"auth_metadata",
|
|
5436
5486
|
{
|
|
5437
5487
|
// Metadata key (primary key)
|
|
5438
|
-
key:
|
|
5488
|
+
key: text12("key").primaryKey(),
|
|
5439
5489
|
// Metadata value
|
|
5440
|
-
value:
|
|
5490
|
+
value: text12("value").notNull(),
|
|
5441
5491
|
// Last updated timestamp — stamped by the database on insert and on update
|
|
5442
5492
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => sql3`now()`)
|
|
5443
5493
|
}
|
|
@@ -5446,8 +5496,8 @@ var init_auth_metadata = __esm({
|
|
|
5446
5496
|
});
|
|
5447
5497
|
|
|
5448
5498
|
// src/server/entities/ops-tokens.ts
|
|
5449
|
-
import { text as
|
|
5450
|
-
import { id as
|
|
5499
|
+
import { text as text13 } from "drizzle-orm/pg-core";
|
|
5500
|
+
import { id as id13, timestamps as timestamps12, utcTimestamp as utcTimestamp9 } from "@spfn/core/db";
|
|
5451
5501
|
var opsTokens;
|
|
5452
5502
|
var init_ops_tokens = __esm({
|
|
5453
5503
|
"src/server/entities/ops-tokens.ts"() {
|
|
@@ -5456,22 +5506,22 @@ var init_ops_tokens = __esm({
|
|
|
5456
5506
|
opsTokens = authSchema.table(
|
|
5457
5507
|
"ops_tokens",
|
|
5458
5508
|
{
|
|
5459
|
-
id:
|
|
5509
|
+
id: id13(),
|
|
5460
5510
|
// Operator-facing label ("ci-deploy", "rayim-laptop")
|
|
5461
|
-
name:
|
|
5511
|
+
name: text13("name").notNull(),
|
|
5462
5512
|
// SHA-256 hex of the token secret. Lookup key — the secret never lands
|
|
5463
5513
|
// here, and the unique constraint doubles as the lookup index.
|
|
5464
|
-
tokenHash:
|
|
5514
|
+
tokenHash: text13("token_hash").notNull().unique(),
|
|
5465
5515
|
// Granted scopes as permission strings ('waitlist:read', ...).
|
|
5466
5516
|
// '*' grants every scope.
|
|
5467
|
-
scopes:
|
|
5517
|
+
scopes: text13("scopes").array().notNull(),
|
|
5468
5518
|
// null = the token does not expire
|
|
5469
|
-
expiresAt:
|
|
5519
|
+
expiresAt: utcTimestamp9("expires_at"),
|
|
5470
5520
|
// null = active; a timestamp revokes the token permanently
|
|
5471
|
-
revokedAt:
|
|
5521
|
+
revokedAt: utcTimestamp9("revoked_at"),
|
|
5472
5522
|
// Last successful verification, updated fire-and-forget
|
|
5473
|
-
lastUsedAt:
|
|
5474
|
-
...
|
|
5523
|
+
lastUsedAt: utcTimestamp9("last_used_at"),
|
|
5524
|
+
...timestamps12()
|
|
5475
5525
|
}
|
|
5476
5526
|
);
|
|
5477
5527
|
}
|
|
@@ -5487,6 +5537,7 @@ var init_entities = __esm({
|
|
|
5487
5537
|
init_user_public_keys();
|
|
5488
5538
|
init_user_social_accounts();
|
|
5489
5539
|
init_verification_codes();
|
|
5540
|
+
init_signup_link_tokens();
|
|
5490
5541
|
init_user_invitations();
|
|
5491
5542
|
init_account_deletion_requests();
|
|
5492
5543
|
init_roles();
|
|
@@ -5513,8 +5564,8 @@ var init_users_repository = __esm({
|
|
|
5513
5564
|
* ID로 사용자 조회
|
|
5514
5565
|
* Read replica 사용
|
|
5515
5566
|
*/
|
|
5516
|
-
async findById(
|
|
5517
|
-
const result = await this.readDb.select().from(users).where(eq(users.id,
|
|
5567
|
+
async findById(id14) {
|
|
5568
|
+
const result = await this.readDb.select().from(users).where(eq(users.id, id14)).limit(1);
|
|
5518
5569
|
return result[0] ?? null;
|
|
5519
5570
|
}
|
|
5520
5571
|
/**
|
|
@@ -5524,8 +5575,8 @@ var init_users_repository = __esm({
|
|
|
5524
5575
|
* 안 되는 게이트(OAuth 세션 발급 등)가 사용한다. 일반 조회는 `findById`(replica)를
|
|
5525
5576
|
* 계속 사용할 것.
|
|
5526
5577
|
*/
|
|
5527
|
-
async findByIdOnPrimary(
|
|
5528
|
-
const result = await this.db.select().from(users).where(eq(users.id,
|
|
5578
|
+
async findByIdOnPrimary(id14) {
|
|
5579
|
+
const result = await this.db.select().from(users).where(eq(users.id, id14)).limit(1);
|
|
5529
5580
|
return result[0] ?? null;
|
|
5530
5581
|
}
|
|
5531
5582
|
/**
|
|
@@ -5596,13 +5647,13 @@ var init_users_repository = __esm({
|
|
|
5596
5647
|
*
|
|
5597
5648
|
* roleId가 null인 유저는 role: null 반환
|
|
5598
5649
|
*/
|
|
5599
|
-
async findByIdWithRole(
|
|
5650
|
+
async findByIdWithRole(id14) {
|
|
5600
5651
|
const result = await this.readDb.select({
|
|
5601
5652
|
user: users,
|
|
5602
5653
|
roleName: roles.name,
|
|
5603
5654
|
roleDisplayName: roles.displayName,
|
|
5604
5655
|
rolePriority: roles.priority
|
|
5605
|
-
}).from(users).leftJoin(roles, eq(users.roleId, roles.id)).where(eq(users.id,
|
|
5656
|
+
}).from(users).leftJoin(roles, eq(users.roleId, roles.id)).where(eq(users.id, id14)).limit(1);
|
|
5606
5657
|
const row = result[0];
|
|
5607
5658
|
if (!row) {
|
|
5608
5659
|
return null;
|
|
@@ -5677,9 +5728,9 @@ var init_users_repository = __esm({
|
|
|
5677
5728
|
* 사용자 정보 업데이트
|
|
5678
5729
|
* Write primary 사용
|
|
5679
5730
|
*/
|
|
5680
|
-
async updateById(
|
|
5731
|
+
async updateById(id14, data) {
|
|
5681
5732
|
const patch = "email" in data ? { ...data, email: normalizeOptionalEmail(data.email) } : data;
|
|
5682
|
-
const result = await this.db.update(users).set(patch).where(eq(users.id,
|
|
5733
|
+
const result = await this.db.update(users).set(patch).where(eq(users.id, id14)).returning();
|
|
5683
5734
|
return result[0] ?? null;
|
|
5684
5735
|
}
|
|
5685
5736
|
/**
|
|
@@ -5691,10 +5742,10 @@ var init_users_repository = __esm({
|
|
|
5691
5742
|
* status가 바뀐 상태) 시 null을 반환하며 예외를 던지지 않는다.
|
|
5692
5743
|
* Write primary 사용
|
|
5693
5744
|
*/
|
|
5694
|
-
async reactivateFromPendingDeletion(
|
|
5745
|
+
async reactivateFromPendingDeletion(id14) {
|
|
5695
5746
|
const result = await this.db.update(users).set({ status: "active" }).where(
|
|
5696
5747
|
and(
|
|
5697
|
-
eq(users.id,
|
|
5748
|
+
eq(users.id, id14),
|
|
5698
5749
|
eq(users.status, "pending_deletion")
|
|
5699
5750
|
)
|
|
5700
5751
|
).returning();
|
|
@@ -5704,32 +5755,32 @@ var init_users_repository = __esm({
|
|
|
5704
5755
|
* 비밀번호 업데이트
|
|
5705
5756
|
* Write primary 사용
|
|
5706
5757
|
*/
|
|
5707
|
-
async updatePassword(
|
|
5758
|
+
async updatePassword(id14, passwordHash, clearPasswordChangeRequired = true) {
|
|
5708
5759
|
const updateData = {
|
|
5709
5760
|
passwordHash
|
|
5710
5761
|
};
|
|
5711
5762
|
if (clearPasswordChangeRequired) {
|
|
5712
5763
|
updateData.passwordChangeRequired = false;
|
|
5713
5764
|
}
|
|
5714
|
-
const result = await this.db.update(users).set(updateData).where(eq(users.id,
|
|
5765
|
+
const result = await this.db.update(users).set(updateData).where(eq(users.id, id14)).returning();
|
|
5715
5766
|
return result[0] ?? null;
|
|
5716
5767
|
}
|
|
5717
5768
|
/**
|
|
5718
5769
|
* 마지막 로그인 시간 업데이트
|
|
5719
5770
|
* Write primary 사용
|
|
5720
5771
|
*/
|
|
5721
|
-
async updateLastLogin(
|
|
5772
|
+
async updateLastLogin(id14) {
|
|
5722
5773
|
const result = await this.db.update(users).set({
|
|
5723
5774
|
lastLoginAt: /* @__PURE__ */ new Date()
|
|
5724
|
-
}).where(eq(users.id,
|
|
5775
|
+
}).where(eq(users.id, id14)).returning();
|
|
5725
5776
|
return result[0] ?? null;
|
|
5726
5777
|
}
|
|
5727
5778
|
/**
|
|
5728
5779
|
* 사용자 삭제
|
|
5729
5780
|
* Write primary 사용
|
|
5730
5781
|
*/
|
|
5731
|
-
async deleteById(
|
|
5732
|
-
const result = await this.db.delete(users).where(eq(users.id,
|
|
5782
|
+
async deleteById(id14) {
|
|
5783
|
+
const result = await this.db.delete(users).where(eq(users.id, id14)).returning();
|
|
5733
5784
|
return result[0] ?? null;
|
|
5734
5785
|
}
|
|
5735
5786
|
/**
|
|
@@ -6077,14 +6128,14 @@ var init_keys_repository = __esm({
|
|
|
6077
6128
|
* stored, so it answers "since when has this device been on this release"
|
|
6078
6129
|
* rather than "when was it last seen", which lastUsedAt already answers.
|
|
6079
6130
|
*/
|
|
6080
|
-
async updateLastUsedById(
|
|
6131
|
+
async updateLastUsedById(id14, identity) {
|
|
6081
6132
|
const staleBefore = new Date(Date.now() - LAST_USED_THROTTLE_MS);
|
|
6082
6133
|
const lastUsedIsStale = or(
|
|
6083
6134
|
isNull(userPublicKeys.lastUsedAt),
|
|
6084
6135
|
lt(userPublicKeys.lastUsedAt, staleBefore)
|
|
6085
6136
|
);
|
|
6086
6137
|
if (!identity) {
|
|
6087
|
-
await this.db.update(userPublicKeys).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(and2(eq2(userPublicKeys.id,
|
|
6138
|
+
await this.db.update(userPublicKeys).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(and2(eq2(userPublicKeys.id, id14), lastUsedIsStale));
|
|
6088
6139
|
return;
|
|
6089
6140
|
}
|
|
6090
6141
|
const identityChanged = sql5`(
|
|
@@ -6101,7 +6152,7 @@ var init_keys_repository = __esm({
|
|
|
6101
6152
|
clientContractVersion: identity.contractVersion,
|
|
6102
6153
|
clientSeenAt: sql5`CASE WHEN ${identityChanged} THEN ${nowParam} ELSE ${userPublicKeys.clientSeenAt} END`
|
|
6103
6154
|
}).where(and2(
|
|
6104
|
-
eq2(userPublicKeys.id,
|
|
6155
|
+
eq2(userPublicKeys.id, id14),
|
|
6105
6156
|
or(lastUsedIsStale, identityChanged)
|
|
6106
6157
|
));
|
|
6107
6158
|
}
|
|
@@ -6140,8 +6191,8 @@ var init_verification_codes_repository = __esm({
|
|
|
6140
6191
|
* ID로 인증 코드 조회
|
|
6141
6192
|
* Read replica 사용
|
|
6142
6193
|
*/
|
|
6143
|
-
async findById(
|
|
6144
|
-
const result = await this.readDb.select().from(verificationCodes).where(eq3(verificationCodes.id,
|
|
6194
|
+
async findById(id14) {
|
|
6195
|
+
const result = await this.readDb.select().from(verificationCodes).where(eq3(verificationCodes.id, id14)).limit(1);
|
|
6145
6196
|
return result[0] ?? null;
|
|
6146
6197
|
}
|
|
6147
6198
|
/**
|
|
@@ -6155,22 +6206,22 @@ var init_verification_codes_repository = __esm({
|
|
|
6155
6206
|
* 인증 코드 사용 처리
|
|
6156
6207
|
* Write primary 사용
|
|
6157
6208
|
*/
|
|
6158
|
-
async markAsUsed(
|
|
6209
|
+
async markAsUsed(id14) {
|
|
6159
6210
|
const result = await this.db.update(verificationCodes).set({
|
|
6160
6211
|
usedAt: /* @__PURE__ */ new Date()
|
|
6161
|
-
}).where(eq3(verificationCodes.id,
|
|
6212
|
+
}).where(eq3(verificationCodes.id, id14)).returning();
|
|
6162
6213
|
return result[0] ?? null;
|
|
6163
6214
|
}
|
|
6164
6215
|
/**
|
|
6165
6216
|
* 시도 횟수 증가
|
|
6166
6217
|
* Write primary 사용
|
|
6167
6218
|
*/
|
|
6168
|
-
async incrementAttempts(
|
|
6169
|
-
const code = await this.findById(
|
|
6219
|
+
async incrementAttempts(id14) {
|
|
6220
|
+
const code = await this.findById(id14);
|
|
6170
6221
|
if (!code) return null;
|
|
6171
6222
|
const result = await this.db.update(verificationCodes).set({
|
|
6172
6223
|
attempts: code.attempts + 1
|
|
6173
|
-
}).where(eq3(verificationCodes.id,
|
|
6224
|
+
}).where(eq3(verificationCodes.id, id14)).returning();
|
|
6174
6225
|
return result[0] ?? null;
|
|
6175
6226
|
}
|
|
6176
6227
|
/**
|
|
@@ -6214,27 +6265,136 @@ var init_verification_codes_repository = __esm({
|
|
|
6214
6265
|
}
|
|
6215
6266
|
});
|
|
6216
6267
|
|
|
6217
|
-
// src/server/repositories/
|
|
6268
|
+
// src/server/repositories/signup-link-tokens.repository.ts
|
|
6218
6269
|
import { BaseRepository as BaseRepository4 } from "@spfn/core/db";
|
|
6219
|
-
import { eq as eq4,
|
|
6270
|
+
import { eq as eq4, and as and4, isNull as isNull3 } from "drizzle-orm";
|
|
6271
|
+
var SignupLinkTokensRepository, signupLinkTokensRepository;
|
|
6272
|
+
var init_signup_link_tokens_repository = __esm({
|
|
6273
|
+
"src/server/repositories/signup-link-tokens.repository.ts"() {
|
|
6274
|
+
"use strict";
|
|
6275
|
+
init_signup_link_tokens();
|
|
6276
|
+
SignupLinkTokensRepository = class extends BaseRepository4 {
|
|
6277
|
+
/**
|
|
6278
|
+
* Create a signup link row.
|
|
6279
|
+
* Write primary.
|
|
6280
|
+
*/
|
|
6281
|
+
async create(data) {
|
|
6282
|
+
const result = await this.db.insert(signupLinkTokens).values(data).returning();
|
|
6283
|
+
return result[0];
|
|
6284
|
+
}
|
|
6285
|
+
/**
|
|
6286
|
+
* Find a row by the hash of an emailed token, in any state.
|
|
6287
|
+
*
|
|
6288
|
+
* Deliberately unfiltered: the caller decides why a link is refused, and a
|
|
6289
|
+
* row filtered out here would be indistinguishable from an unknown token in
|
|
6290
|
+
* the logs.
|
|
6291
|
+
*
|
|
6292
|
+
* Read replica.
|
|
6293
|
+
*/
|
|
6294
|
+
async findByTokenHash(tokenHash) {
|
|
6295
|
+
const result = await this.readDb.select().from(signupLinkTokens).where(eq4(signupLinkTokens.tokenHash, tokenHash)).limit(1);
|
|
6296
|
+
return result[0] ?? null;
|
|
6297
|
+
}
|
|
6298
|
+
/**
|
|
6299
|
+
* Find a row by the hash of a setup session secret, in any state.
|
|
6300
|
+
* Read replica.
|
|
6301
|
+
*/
|
|
6302
|
+
async findBySetupSecretHash(setupSecretHash) {
|
|
6303
|
+
const result = await this.readDb.select().from(signupLinkTokens).where(eq4(signupLinkTokens.setupSecretHash, setupSecretHash)).limit(1);
|
|
6304
|
+
return result[0] ?? null;
|
|
6305
|
+
}
|
|
6306
|
+
/**
|
|
6307
|
+
* Consume a link and open a setup session on it, but only if it is still
|
|
6308
|
+
* unconsumed and not superseded.
|
|
6309
|
+
*
|
|
6310
|
+
* @returns the updated row, or null if another request claimed it first
|
|
6311
|
+
*/
|
|
6312
|
+
async claimLink(id14, setupSecretHash, setupExpiresAt) {
|
|
6313
|
+
const result = await this.db.update(signupLinkTokens).set({
|
|
6314
|
+
consumedAt: /* @__PURE__ */ new Date(),
|
|
6315
|
+
setupSecretHash,
|
|
6316
|
+
setupExpiresAt
|
|
6317
|
+
}).where(
|
|
6318
|
+
and4(
|
|
6319
|
+
eq4(signupLinkTokens.id, id14),
|
|
6320
|
+
isNull3(signupLinkTokens.consumedAt),
|
|
6321
|
+
isNull3(signupLinkTokens.supersededAt)
|
|
6322
|
+
)
|
|
6323
|
+
).returning();
|
|
6324
|
+
return result[0] ?? null;
|
|
6325
|
+
}
|
|
6326
|
+
/**
|
|
6327
|
+
* Mark a setup session as completed, but only if it has not completed
|
|
6328
|
+
* already.
|
|
6329
|
+
*
|
|
6330
|
+
* @returns the updated row, or null if another request completed it first
|
|
6331
|
+
*/
|
|
6332
|
+
async claimSetupSession(id14) {
|
|
6333
|
+
const result = await this.db.update(signupLinkTokens).set({ completedAt: /* @__PURE__ */ new Date() }).where(
|
|
6334
|
+
and4(
|
|
6335
|
+
eq4(signupLinkTokens.id, id14),
|
|
6336
|
+
isNull3(signupLinkTokens.completedAt),
|
|
6337
|
+
isNull3(signupLinkTokens.supersededAt)
|
|
6338
|
+
)
|
|
6339
|
+
).returning();
|
|
6340
|
+
return result[0] ?? null;
|
|
6341
|
+
}
|
|
6342
|
+
/**
|
|
6343
|
+
* Supersede every live link for an address, so requesting a new one
|
|
6344
|
+
* invalidates the previous one and any setup session opened from it.
|
|
6345
|
+
*
|
|
6346
|
+
* Write primary.
|
|
6347
|
+
*
|
|
6348
|
+
* @returns number of rows superseded
|
|
6349
|
+
*/
|
|
6350
|
+
async supersedeLiveForEmail(email) {
|
|
6351
|
+
const result = await this.db.update(signupLinkTokens).set({ supersededAt: /* @__PURE__ */ new Date() }).where(
|
|
6352
|
+
and4(
|
|
6353
|
+
eq4(signupLinkTokens.email, email),
|
|
6354
|
+
isNull3(signupLinkTokens.supersededAt),
|
|
6355
|
+
isNull3(signupLinkTokens.completedAt)
|
|
6356
|
+
)
|
|
6357
|
+
).returning();
|
|
6358
|
+
return result.length;
|
|
6359
|
+
}
|
|
6360
|
+
/**
|
|
6361
|
+
* Delete every signup link row for an address (account destruction).
|
|
6362
|
+
*
|
|
6363
|
+
* Rows key on the email text and carry no user FK, so destruction has to
|
|
6364
|
+
* clean them up by address the way verification codes are cleaned up.
|
|
6365
|
+
*
|
|
6366
|
+
* Write primary.
|
|
6367
|
+
*/
|
|
6368
|
+
async deleteByEmail(email) {
|
|
6369
|
+
const result = await this.db.delete(signupLinkTokens).where(eq4(signupLinkTokens.email, email)).returning();
|
|
6370
|
+
return result.length;
|
|
6371
|
+
}
|
|
6372
|
+
};
|
|
6373
|
+
signupLinkTokensRepository = new SignupLinkTokensRepository();
|
|
6374
|
+
}
|
|
6375
|
+
});
|
|
6376
|
+
|
|
6377
|
+
// src/server/repositories/roles.repository.ts
|
|
6378
|
+
import { BaseRepository as BaseRepository5 } from "@spfn/core/db";
|
|
6379
|
+
import { eq as eq5, asc } from "drizzle-orm";
|
|
6220
6380
|
var RolesRepository, rolesRepository;
|
|
6221
6381
|
var init_roles_repository = __esm({
|
|
6222
6382
|
"src/server/repositories/roles.repository.ts"() {
|
|
6223
6383
|
"use strict";
|
|
6224
6384
|
init_roles();
|
|
6225
|
-
RolesRepository = class extends
|
|
6385
|
+
RolesRepository = class extends BaseRepository5 {
|
|
6226
6386
|
/**
|
|
6227
6387
|
* ID로 역할 조회
|
|
6228
6388
|
*/
|
|
6229
|
-
async findById(
|
|
6230
|
-
const result = await this.readDb.select().from(roles).where(
|
|
6389
|
+
async findById(id14) {
|
|
6390
|
+
const result = await this.readDb.select().from(roles).where(eq5(roles.id, id14)).limit(1);
|
|
6231
6391
|
return result[0] ?? null;
|
|
6232
6392
|
}
|
|
6233
6393
|
/**
|
|
6234
6394
|
* Name으로 역할 조회
|
|
6235
6395
|
*/
|
|
6236
6396
|
async findByName(name) {
|
|
6237
|
-
const result = await this.readDb.select().from(roles).where(
|
|
6397
|
+
const result = await this.readDb.select().from(roles).where(eq5(roles.name, name)).limit(1);
|
|
6238
6398
|
return result[0] ?? null;
|
|
6239
6399
|
}
|
|
6240
6400
|
/**
|
|
@@ -6247,7 +6407,7 @@ var init_roles_repository = __esm({
|
|
|
6247
6407
|
* 활성 역할만 조회
|
|
6248
6408
|
*/
|
|
6249
6409
|
async findActive() {
|
|
6250
|
-
return this.readDb.select().from(roles).where(
|
|
6410
|
+
return this.readDb.select().from(roles).where(eq5(roles.isActive, true)).orderBy(asc(roles.priority));
|
|
6251
6411
|
}
|
|
6252
6412
|
/**
|
|
6253
6413
|
* 역할 생성
|
|
@@ -6258,15 +6418,15 @@ var init_roles_repository = __esm({
|
|
|
6258
6418
|
/**
|
|
6259
6419
|
* 역할 업데이트
|
|
6260
6420
|
*/
|
|
6261
|
-
async updateById(
|
|
6262
|
-
const result = await this.db.update(roles).set(data).where(
|
|
6421
|
+
async updateById(id14, data) {
|
|
6422
|
+
const result = await this.db.update(roles).set(data).where(eq5(roles.id, id14)).returning();
|
|
6263
6423
|
return result[0] ?? null;
|
|
6264
6424
|
}
|
|
6265
6425
|
/**
|
|
6266
6426
|
* 역할 삭제
|
|
6267
6427
|
*/
|
|
6268
|
-
async deleteById(
|
|
6269
|
-
const result = await this.db.delete(roles).where(
|
|
6428
|
+
async deleteById(id14) {
|
|
6429
|
+
const result = await this.db.delete(roles).where(eq5(roles.id, id14)).returning();
|
|
6270
6430
|
return result[0] ?? null;
|
|
6271
6431
|
}
|
|
6272
6432
|
};
|
|
@@ -6275,26 +6435,26 @@ var init_roles_repository = __esm({
|
|
|
6275
6435
|
});
|
|
6276
6436
|
|
|
6277
6437
|
// src/server/repositories/permissions.repository.ts
|
|
6278
|
-
import { BaseRepository as
|
|
6279
|
-
import { asc as asc2, eq as
|
|
6438
|
+
import { BaseRepository as BaseRepository6 } from "@spfn/core/db";
|
|
6439
|
+
import { asc as asc2, eq as eq6, inArray } from "drizzle-orm";
|
|
6280
6440
|
var PermissionsRepository, permissionsRepository;
|
|
6281
6441
|
var init_permissions_repository = __esm({
|
|
6282
6442
|
"src/server/repositories/permissions.repository.ts"() {
|
|
6283
6443
|
"use strict";
|
|
6284
6444
|
init_permissions();
|
|
6285
|
-
PermissionsRepository = class extends
|
|
6445
|
+
PermissionsRepository = class extends BaseRepository6 {
|
|
6286
6446
|
/**
|
|
6287
6447
|
* ID로 권한 조회
|
|
6288
6448
|
*/
|
|
6289
|
-
async findById(
|
|
6290
|
-
const result = await this.readDb.select().from(permissions).where(
|
|
6449
|
+
async findById(id14) {
|
|
6450
|
+
const result = await this.readDb.select().from(permissions).where(eq6(permissions.id, id14)).limit(1);
|
|
6291
6451
|
return result[0] ?? null;
|
|
6292
6452
|
}
|
|
6293
6453
|
/**
|
|
6294
6454
|
* Name으로 권한 조회
|
|
6295
6455
|
*/
|
|
6296
6456
|
async findByName(name) {
|
|
6297
|
-
const result = await this.readDb.select().from(permissions).where(
|
|
6457
|
+
const result = await this.readDb.select().from(permissions).where(eq6(permissions.name, name)).limit(1);
|
|
6298
6458
|
return result[0] ?? null;
|
|
6299
6459
|
}
|
|
6300
6460
|
/**
|
|
@@ -6314,13 +6474,13 @@ var init_permissions_repository = __esm({
|
|
|
6314
6474
|
* 활성 권한만 조회
|
|
6315
6475
|
*/
|
|
6316
6476
|
async findActive() {
|
|
6317
|
-
return this.readDb.select().from(permissions).where(
|
|
6477
|
+
return this.readDb.select().from(permissions).where(eq6(permissions.isActive, true)).orderBy(asc2(permissions.name));
|
|
6318
6478
|
}
|
|
6319
6479
|
/**
|
|
6320
6480
|
* 카테고리별 권한 조회
|
|
6321
6481
|
*/
|
|
6322
6482
|
async findByCategory(category) {
|
|
6323
|
-
return this.readDb.select().from(permissions).where(
|
|
6483
|
+
return this.readDb.select().from(permissions).where(eq6(permissions.category, category)).orderBy(asc2(permissions.name));
|
|
6324
6484
|
}
|
|
6325
6485
|
/**
|
|
6326
6486
|
* 권한 생성
|
|
@@ -6338,15 +6498,15 @@ var init_permissions_repository = __esm({
|
|
|
6338
6498
|
/**
|
|
6339
6499
|
* 권한 업데이트
|
|
6340
6500
|
*/
|
|
6341
|
-
async updateById(
|
|
6342
|
-
const result = await this.db.update(permissions).set(data).where(
|
|
6501
|
+
async updateById(id14, data) {
|
|
6502
|
+
const result = await this.db.update(permissions).set(data).where(eq6(permissions.id, id14)).returning();
|
|
6343
6503
|
return result[0] ?? null;
|
|
6344
6504
|
}
|
|
6345
6505
|
/**
|
|
6346
6506
|
* 권한 삭제
|
|
6347
6507
|
*/
|
|
6348
|
-
async deleteById(
|
|
6349
|
-
const result = await this.db.delete(permissions).where(
|
|
6508
|
+
async deleteById(id14) {
|
|
6509
|
+
const result = await this.db.delete(permissions).where(eq6(permissions.id, id14)).returning();
|
|
6350
6510
|
return result[0] ?? null;
|
|
6351
6511
|
}
|
|
6352
6512
|
};
|
|
@@ -6355,25 +6515,25 @@ var init_permissions_repository = __esm({
|
|
|
6355
6515
|
});
|
|
6356
6516
|
|
|
6357
6517
|
// src/server/repositories/role-permissions.repository.ts
|
|
6358
|
-
import { BaseRepository as
|
|
6359
|
-
import { and as
|
|
6518
|
+
import { BaseRepository as BaseRepository7 } from "@spfn/core/db";
|
|
6519
|
+
import { and as and5, eq as eq7 } from "drizzle-orm";
|
|
6360
6520
|
var RolePermissionsRepository, rolePermissionsRepository;
|
|
6361
6521
|
var init_role_permissions_repository = __esm({
|
|
6362
6522
|
"src/server/repositories/role-permissions.repository.ts"() {
|
|
6363
6523
|
"use strict";
|
|
6364
6524
|
init_role_permissions();
|
|
6365
|
-
RolePermissionsRepository = class extends
|
|
6525
|
+
RolePermissionsRepository = class extends BaseRepository7 {
|
|
6366
6526
|
/**
|
|
6367
6527
|
* 역할 ID로 모든 권한 조회
|
|
6368
6528
|
*/
|
|
6369
6529
|
async findByRoleId(roleId) {
|
|
6370
|
-
return this.readDb.select().from(rolePermissions).where(
|
|
6530
|
+
return this.readDb.select().from(rolePermissions).where(eq7(rolePermissions.roleId, roleId));
|
|
6371
6531
|
}
|
|
6372
6532
|
/**
|
|
6373
6533
|
* 권한 ID로 모든 역할 조회
|
|
6374
6534
|
*/
|
|
6375
6535
|
async findByPermissionId(permissionId) {
|
|
6376
|
-
return this.readDb.select().from(rolePermissions).where(
|
|
6536
|
+
return this.readDb.select().from(rolePermissions).where(eq7(rolePermissions.permissionId, permissionId));
|
|
6377
6537
|
}
|
|
6378
6538
|
/**
|
|
6379
6539
|
* 역할-권한 매핑 생성
|
|
@@ -6393,9 +6553,9 @@ var init_role_permissions_repository = __esm({
|
|
|
6393
6553
|
*/
|
|
6394
6554
|
async deleteByRoleIdAndPermissionId(roleId, permissionId) {
|
|
6395
6555
|
const result = await this.db.delete(rolePermissions).where(
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6556
|
+
and5(
|
|
6557
|
+
eq7(rolePermissions.roleId, roleId),
|
|
6558
|
+
eq7(rolePermissions.permissionId, permissionId)
|
|
6399
6559
|
)
|
|
6400
6560
|
).returning();
|
|
6401
6561
|
return result[0] ?? null;
|
|
@@ -6404,7 +6564,7 @@ var init_role_permissions_repository = __esm({
|
|
|
6404
6564
|
* 역할의 모든 권한 매핑 삭제
|
|
6405
6565
|
*/
|
|
6406
6566
|
async deleteByRoleId(roleId) {
|
|
6407
|
-
const result = await this.db.delete(rolePermissions).where(
|
|
6567
|
+
const result = await this.db.delete(rolePermissions).where(eq7(rolePermissions.roleId, roleId)).returning();
|
|
6408
6568
|
return result.length;
|
|
6409
6569
|
}
|
|
6410
6570
|
/**
|
|
@@ -6425,19 +6585,19 @@ var init_role_permissions_repository = __esm({
|
|
|
6425
6585
|
});
|
|
6426
6586
|
|
|
6427
6587
|
// src/server/repositories/user-permissions.repository.ts
|
|
6428
|
-
import { BaseRepository as
|
|
6429
|
-
import { eq as
|
|
6588
|
+
import { BaseRepository as BaseRepository8 } from "@spfn/core/db";
|
|
6589
|
+
import { eq as eq8, and as and6, or as or2, isNull as isNull4, isNotNull, lt as lt3, gt as gt2 } from "drizzle-orm";
|
|
6430
6590
|
var UserPermissionsRepository, userPermissionsRepository;
|
|
6431
6591
|
var init_user_permissions_repository = __esm({
|
|
6432
6592
|
"src/server/repositories/user-permissions.repository.ts"() {
|
|
6433
6593
|
"use strict";
|
|
6434
6594
|
init_user_permissions();
|
|
6435
|
-
UserPermissionsRepository = class extends
|
|
6595
|
+
UserPermissionsRepository = class extends BaseRepository8 {
|
|
6436
6596
|
/**
|
|
6437
6597
|
* 사용자 ID로 모든 권한 오버라이드 조회
|
|
6438
6598
|
*/
|
|
6439
6599
|
async findByUserId(userId) {
|
|
6440
|
-
return this.readDb.select().from(userPermissions).where(
|
|
6600
|
+
return this.readDb.select().from(userPermissions).where(eq8(userPermissions.userId, userId));
|
|
6441
6601
|
}
|
|
6442
6602
|
/**
|
|
6443
6603
|
* 사용자 ID로 유효한 권한 오버라이드만 조회
|
|
@@ -6446,10 +6606,10 @@ var init_user_permissions_repository = __esm({
|
|
|
6446
6606
|
async findValidByUserId(userId) {
|
|
6447
6607
|
const now = /* @__PURE__ */ new Date();
|
|
6448
6608
|
return this.readDb.select().from(userPermissions).where(
|
|
6449
|
-
|
|
6450
|
-
|
|
6609
|
+
and6(
|
|
6610
|
+
eq8(userPermissions.userId, userId),
|
|
6451
6611
|
or2(
|
|
6452
|
-
|
|
6612
|
+
isNull4(userPermissions.expiresAt),
|
|
6453
6613
|
gt2(userPermissions.expiresAt, now)
|
|
6454
6614
|
)
|
|
6455
6615
|
)
|
|
@@ -6460,9 +6620,9 @@ var init_user_permissions_repository = __esm({
|
|
|
6460
6620
|
*/
|
|
6461
6621
|
async findByUserIdAndPermissionId(userId, permissionId) {
|
|
6462
6622
|
const result = await this.readDb.select().from(userPermissions).where(
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6623
|
+
and6(
|
|
6624
|
+
eq8(userPermissions.userId, userId),
|
|
6625
|
+
eq8(userPermissions.permissionId, permissionId)
|
|
6466
6626
|
)
|
|
6467
6627
|
).limit(1);
|
|
6468
6628
|
return result[0] ?? null;
|
|
@@ -6476,8 +6636,8 @@ var init_user_permissions_repository = __esm({
|
|
|
6476
6636
|
/**
|
|
6477
6637
|
* 사용자 권한 오버라이드 업데이트
|
|
6478
6638
|
*/
|
|
6479
|
-
async updateById(
|
|
6480
|
-
const result = await this.db.update(userPermissions).set(data).where(
|
|
6639
|
+
async updateById(id14, data) {
|
|
6640
|
+
const result = await this.db.update(userPermissions).set(data).where(eq8(userPermissions.id, id14)).returning();
|
|
6481
6641
|
return result[0] ?? null;
|
|
6482
6642
|
}
|
|
6483
6643
|
/**
|
|
@@ -6485,9 +6645,9 @@ var init_user_permissions_repository = __esm({
|
|
|
6485
6645
|
*/
|
|
6486
6646
|
async deleteByUserIdAndPermissionId(userId, permissionId) {
|
|
6487
6647
|
const result = await this.db.delete(userPermissions).where(
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6648
|
+
and6(
|
|
6649
|
+
eq8(userPermissions.userId, userId),
|
|
6650
|
+
eq8(userPermissions.permissionId, permissionId)
|
|
6491
6651
|
)
|
|
6492
6652
|
).returning();
|
|
6493
6653
|
return result[0] ?? null;
|
|
@@ -6496,7 +6656,7 @@ var init_user_permissions_repository = __esm({
|
|
|
6496
6656
|
* 사용자의 모든 권한 오버라이드 삭제
|
|
6497
6657
|
*/
|
|
6498
6658
|
async deleteByUserId(userId) {
|
|
6499
|
-
const result = await this.db.delete(userPermissions).where(
|
|
6659
|
+
const result = await this.db.delete(userPermissions).where(eq8(userPermissions.userId, userId)).returning();
|
|
6500
6660
|
return result.length;
|
|
6501
6661
|
}
|
|
6502
6662
|
/**
|
|
@@ -6505,7 +6665,7 @@ var init_user_permissions_repository = __esm({
|
|
|
6505
6665
|
async deleteExpired() {
|
|
6506
6666
|
const now = /* @__PURE__ */ new Date();
|
|
6507
6667
|
const result = await this.db.delete(userPermissions).where(
|
|
6508
|
-
|
|
6668
|
+
and6(
|
|
6509
6669
|
isNotNull(userPermissions.expiresAt),
|
|
6510
6670
|
lt3(userPermissions.expiresAt, now)
|
|
6511
6671
|
)
|
|
@@ -6518,33 +6678,33 @@ var init_user_permissions_repository = __esm({
|
|
|
6518
6678
|
});
|
|
6519
6679
|
|
|
6520
6680
|
// src/server/repositories/user-profiles.repository.ts
|
|
6521
|
-
import { BaseRepository as
|
|
6522
|
-
import { eq as
|
|
6681
|
+
import { BaseRepository as BaseRepository9 } from "@spfn/core/db";
|
|
6682
|
+
import { eq as eq9 } from "drizzle-orm";
|
|
6523
6683
|
var UserProfilesRepository, userProfilesRepository;
|
|
6524
6684
|
var init_user_profiles_repository = __esm({
|
|
6525
6685
|
"src/server/repositories/user-profiles.repository.ts"() {
|
|
6526
6686
|
"use strict";
|
|
6527
6687
|
init_user_profiles();
|
|
6528
|
-
UserProfilesRepository = class extends
|
|
6688
|
+
UserProfilesRepository = class extends BaseRepository9 {
|
|
6529
6689
|
/**
|
|
6530
6690
|
* ID로 프로필 조회
|
|
6531
6691
|
*/
|
|
6532
|
-
async findById(
|
|
6533
|
-
const result = await this.readDb.select().from(userProfiles).where(
|
|
6692
|
+
async findById(id14) {
|
|
6693
|
+
const result = await this.readDb.select().from(userProfiles).where(eq9(userProfiles.id, id14)).limit(1);
|
|
6534
6694
|
return result[0] ?? null;
|
|
6535
6695
|
}
|
|
6536
6696
|
/**
|
|
6537
6697
|
* User ID로 locale만 조회 (경량)
|
|
6538
6698
|
*/
|
|
6539
6699
|
async findLocaleByUserId(userId) {
|
|
6540
|
-
const result = await this.readDb.select({ locale: userProfiles.locale }).from(userProfiles).where(
|
|
6700
|
+
const result = await this.readDb.select({ locale: userProfiles.locale }).from(userProfiles).where(eq9(userProfiles.userId, userId)).limit(1);
|
|
6541
6701
|
return result[0]?.locale || "en";
|
|
6542
6702
|
}
|
|
6543
6703
|
/**
|
|
6544
6704
|
* User ID로 프로필 조회
|
|
6545
6705
|
*/
|
|
6546
6706
|
async findByUserId(userId) {
|
|
6547
|
-
const result = await this.readDb.select().from(userProfiles).where(
|
|
6707
|
+
const result = await this.readDb.select().from(userProfiles).where(eq9(userProfiles.userId, userId)).limit(1);
|
|
6548
6708
|
return result[0] ?? null;
|
|
6549
6709
|
}
|
|
6550
6710
|
/**
|
|
@@ -6556,29 +6716,29 @@ var init_user_profiles_repository = __esm({
|
|
|
6556
6716
|
/**
|
|
6557
6717
|
* 프로필 업데이트 (by ID)
|
|
6558
6718
|
*/
|
|
6559
|
-
async updateById(
|
|
6560
|
-
const result = await this.db.update(userProfiles).set(data).where(
|
|
6719
|
+
async updateById(id14, data) {
|
|
6720
|
+
const result = await this.db.update(userProfiles).set(data).where(eq9(userProfiles.id, id14)).returning();
|
|
6561
6721
|
return result[0] ?? null;
|
|
6562
6722
|
}
|
|
6563
6723
|
/**
|
|
6564
6724
|
* 프로필 업데이트 (by User ID)
|
|
6565
6725
|
*/
|
|
6566
6726
|
async updateByUserId(userId, data) {
|
|
6567
|
-
const result = await this.db.update(userProfiles).set(data).where(
|
|
6727
|
+
const result = await this.db.update(userProfiles).set(data).where(eq9(userProfiles.userId, userId)).returning();
|
|
6568
6728
|
return result[0] ?? null;
|
|
6569
6729
|
}
|
|
6570
6730
|
/**
|
|
6571
6731
|
* 프로필 삭제 (by ID)
|
|
6572
6732
|
*/
|
|
6573
|
-
async deleteById(
|
|
6574
|
-
const result = await this.db.delete(userProfiles).where(
|
|
6733
|
+
async deleteById(id14) {
|
|
6734
|
+
const result = await this.db.delete(userProfiles).where(eq9(userProfiles.id, id14)).returning();
|
|
6575
6735
|
return result[0] ?? null;
|
|
6576
6736
|
}
|
|
6577
6737
|
/**
|
|
6578
6738
|
* 프로필 삭제 (by User ID)
|
|
6579
6739
|
*/
|
|
6580
6740
|
async deleteByUserId(userId) {
|
|
6581
|
-
const result = await this.db.delete(userProfiles).where(
|
|
6741
|
+
const result = await this.db.delete(userProfiles).where(eq9(userProfiles.userId, userId)).returning();
|
|
6582
6742
|
return result[0] ?? null;
|
|
6583
6743
|
}
|
|
6584
6744
|
/**
|
|
@@ -6620,7 +6780,7 @@ var init_user_profiles_repository = __esm({
|
|
|
6620
6780
|
metadata: userProfiles.metadata,
|
|
6621
6781
|
createdAt: userProfiles.createdAt,
|
|
6622
6782
|
updatedAt: userProfiles.updatedAt
|
|
6623
|
-
}).from(userProfiles).where(
|
|
6783
|
+
}).from(userProfiles).where(eq9(userProfiles.userId, userId)).limit(1).then((rows) => rows[0] ?? null);
|
|
6624
6784
|
if (!profile) {
|
|
6625
6785
|
return null;
|
|
6626
6786
|
}
|
|
@@ -6648,8 +6808,8 @@ var init_user_profiles_repository = __esm({
|
|
|
6648
6808
|
});
|
|
6649
6809
|
|
|
6650
6810
|
// src/server/repositories/invitations.repository.ts
|
|
6651
|
-
import { eq as
|
|
6652
|
-
import { BaseRepository as
|
|
6811
|
+
import { eq as eq10, and as and7, lt as lt4, desc as desc2, sql as sql6 } from "drizzle-orm";
|
|
6812
|
+
import { BaseRepository as BaseRepository10 } from "@spfn/core/db";
|
|
6653
6813
|
var InvitationsRepository, invitationsRepository;
|
|
6654
6814
|
var init_invitations_repository = __esm({
|
|
6655
6815
|
"src/server/repositories/invitations.repository.ts"() {
|
|
@@ -6658,19 +6818,19 @@ var init_invitations_repository = __esm({
|
|
|
6658
6818
|
init_roles();
|
|
6659
6819
|
init_user_invitations();
|
|
6660
6820
|
init_email();
|
|
6661
|
-
InvitationsRepository = class extends
|
|
6821
|
+
InvitationsRepository = class extends BaseRepository10 {
|
|
6662
6822
|
/**
|
|
6663
6823
|
* ID로 초대 조회
|
|
6664
6824
|
*/
|
|
6665
|
-
async findById(
|
|
6666
|
-
const result = await this.readDb.select().from(userInvitations).where(
|
|
6825
|
+
async findById(id14) {
|
|
6826
|
+
const result = await this.readDb.select().from(userInvitations).where(eq10(userInvitations.id, id14)).limit(1);
|
|
6667
6827
|
return result[0] ?? null;
|
|
6668
6828
|
}
|
|
6669
6829
|
/**
|
|
6670
6830
|
* Token으로 초대 조회
|
|
6671
6831
|
*/
|
|
6672
6832
|
async findByToken(token) {
|
|
6673
|
-
const result = await this.readDb.select().from(userInvitations).where(
|
|
6833
|
+
const result = await this.readDb.select().from(userInvitations).where(eq10(userInvitations.token, token)).limit(1);
|
|
6674
6834
|
return result[0] ?? null;
|
|
6675
6835
|
}
|
|
6676
6836
|
/**
|
|
@@ -6678,9 +6838,9 @@ var init_invitations_repository = __esm({
|
|
|
6678
6838
|
*/
|
|
6679
6839
|
async findPendingByEmail(email) {
|
|
6680
6840
|
const result = await this.readDb.select().from(userInvitations).where(
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
|
|
6841
|
+
and7(
|
|
6842
|
+
eq10(userInvitations.email, normalizeEmail(email)),
|
|
6843
|
+
eq10(userInvitations.status, "pending")
|
|
6684
6844
|
)
|
|
6685
6845
|
).limit(1);
|
|
6686
6846
|
return result[0] ?? null;
|
|
@@ -6689,13 +6849,13 @@ var init_invitations_repository = __esm({
|
|
|
6689
6849
|
* 초대자 ID로 모든 초대 조회
|
|
6690
6850
|
*/
|
|
6691
6851
|
async findByInvitedBy(invitedBy) {
|
|
6692
|
-
return this.readDb.select().from(userInvitations).where(
|
|
6852
|
+
return this.readDb.select().from(userInvitations).where(eq10(userInvitations.invitedBy, invitedBy));
|
|
6693
6853
|
}
|
|
6694
6854
|
/**
|
|
6695
6855
|
* 상태별 초대 조회
|
|
6696
6856
|
*/
|
|
6697
6857
|
async findByStatus(status) {
|
|
6698
|
-
return this.readDb.select().from(userInvitations).where(
|
|
6858
|
+
return this.readDb.select().from(userInvitations).where(eq10(userInvitations.status, status));
|
|
6699
6859
|
}
|
|
6700
6860
|
/**
|
|
6701
6861
|
* 초대 생성
|
|
@@ -6706,7 +6866,7 @@ var init_invitations_repository = __esm({
|
|
|
6706
6866
|
/**
|
|
6707
6867
|
* 초대 상태 업데이트
|
|
6708
6868
|
*/
|
|
6709
|
-
async updateStatus(
|
|
6869
|
+
async updateStatus(id14, status, timestamp2) {
|
|
6710
6870
|
const updates = {
|
|
6711
6871
|
status
|
|
6712
6872
|
};
|
|
@@ -6717,14 +6877,14 @@ var init_invitations_repository = __esm({
|
|
|
6717
6877
|
updates.cancelledAt = timestamp2;
|
|
6718
6878
|
}
|
|
6719
6879
|
}
|
|
6720
|
-
const result = await this.db.update(userInvitations).set(updates).where(
|
|
6880
|
+
const result = await this.db.update(userInvitations).set(updates).where(eq10(userInvitations.id, id14)).returning();
|
|
6721
6881
|
return result[0] ?? null;
|
|
6722
6882
|
}
|
|
6723
6883
|
/**
|
|
6724
6884
|
* 초대 삭제
|
|
6725
6885
|
*/
|
|
6726
|
-
async deleteById(
|
|
6727
|
-
const result = await this.db.delete(userInvitations).where(
|
|
6886
|
+
async deleteById(id14) {
|
|
6887
|
+
const result = await this.db.delete(userInvitations).where(eq10(userInvitations.id, id14)).returning();
|
|
6728
6888
|
return result[0] ?? null;
|
|
6729
6889
|
}
|
|
6730
6890
|
/**
|
|
@@ -6733,8 +6893,8 @@ var init_invitations_repository = __esm({
|
|
|
6733
6893
|
async updateExpiredInvitations() {
|
|
6734
6894
|
const now = /* @__PURE__ */ new Date();
|
|
6735
6895
|
const result = await this.db.update(userInvitations).set({ status: "expired" }).where(
|
|
6736
|
-
|
|
6737
|
-
|
|
6896
|
+
and7(
|
|
6897
|
+
eq10(userInvitations.status, "pending"),
|
|
6738
6898
|
lt4(userInvitations.expiresAt, now)
|
|
6739
6899
|
)
|
|
6740
6900
|
).returning();
|
|
@@ -6766,7 +6926,7 @@ var init_invitations_repository = __esm({
|
|
|
6766
6926
|
id: users.id,
|
|
6767
6927
|
email: users.email
|
|
6768
6928
|
}
|
|
6769
|
-
}).from(userInvitations).innerJoin(roles,
|
|
6929
|
+
}).from(userInvitations).innerJoin(roles, eq10(userInvitations.roleId, roles.id)).innerJoin(users, eq10(userInvitations.invitedBy, users.id)).where(eq10(userInvitations.token, token)).limit(1);
|
|
6770
6930
|
return result[0] ?? null;
|
|
6771
6931
|
}
|
|
6772
6932
|
/**
|
|
@@ -6777,12 +6937,12 @@ var init_invitations_repository = __esm({
|
|
|
6777
6937
|
const offset = (page - 1) * limit;
|
|
6778
6938
|
const conditions = [];
|
|
6779
6939
|
if (status) {
|
|
6780
|
-
conditions.push(
|
|
6940
|
+
conditions.push(eq10(userInvitations.status, status));
|
|
6781
6941
|
}
|
|
6782
6942
|
if (invitedBy) {
|
|
6783
|
-
conditions.push(
|
|
6943
|
+
conditions.push(eq10(userInvitations.invitedBy, invitedBy));
|
|
6784
6944
|
}
|
|
6785
|
-
const whereClause = conditions.length > 0 ?
|
|
6945
|
+
const whereClause = conditions.length > 0 ? and7(...conditions) : void 0;
|
|
6786
6946
|
const countResult = await this.readDb.select({ count: sql6`count(*)` }).from(userInvitations).where(whereClause);
|
|
6787
6947
|
const total = Number(countResult[0]?.count || 0);
|
|
6788
6948
|
const results = await this.readDb.select({
|
|
@@ -6807,7 +6967,7 @@ var init_invitations_repository = __esm({
|
|
|
6807
6967
|
id: users.id,
|
|
6808
6968
|
email: users.email
|
|
6809
6969
|
}
|
|
6810
|
-
}).from(userInvitations).innerJoin(roles,
|
|
6970
|
+
}).from(userInvitations).innerJoin(roles, eq10(userInvitations.roleId, roles.id)).innerJoin(users, eq10(userInvitations.invitedBy, users.id)).where(whereClause).orderBy(desc2(userInvitations.createdAt)).limit(limit).offset(offset);
|
|
6811
6971
|
return {
|
|
6812
6972
|
invitations: results,
|
|
6813
6973
|
total,
|
|
@@ -6819,31 +6979,31 @@ var init_invitations_repository = __esm({
|
|
|
6819
6979
|
/**
|
|
6820
6980
|
* 초대 업데이트 (일반 업데이트 - 모든 필드 가능)
|
|
6821
6981
|
*/
|
|
6822
|
-
async updateById(
|
|
6982
|
+
async updateById(id14, data) {
|
|
6823
6983
|
const patch = "email" in data && typeof data.email === "string" ? { ...data, email: normalizeEmail(data.email) } : data;
|
|
6824
|
-
const result = await this.db.update(userInvitations).set(patch).where(
|
|
6984
|
+
const result = await this.db.update(userInvitations).set(patch).where(eq10(userInvitations.id, id14)).returning();
|
|
6825
6985
|
return result[0] ?? null;
|
|
6826
6986
|
}
|
|
6827
6987
|
/**
|
|
6828
6988
|
* 초대 재전송 (status와 expiresAt 동시 업데이트)
|
|
6829
6989
|
*/
|
|
6830
|
-
async resend(
|
|
6990
|
+
async resend(id14, newExpiresAt) {
|
|
6831
6991
|
const result = await this.db.update(userInvitations).set({
|
|
6832
6992
|
status: "pending",
|
|
6833
6993
|
expiresAt: newExpiresAt
|
|
6834
|
-
}).where(
|
|
6994
|
+
}).where(eq10(userInvitations.id, id14)).returning();
|
|
6835
6995
|
return result[0] ?? null;
|
|
6836
6996
|
}
|
|
6837
6997
|
/**
|
|
6838
6998
|
* 초대 취소 (status, metadata 동시 업데이트)
|
|
6839
6999
|
*/
|
|
6840
|
-
async cancel(
|
|
7000
|
+
async cancel(id14, cancelledBy, reason, currentMetadata) {
|
|
6841
7001
|
const newMetadata = currentMetadata ? { ...currentMetadata, cancelReason: reason, cancelledBy } : { cancelReason: reason, cancelledBy };
|
|
6842
7002
|
const result = await this.db.update(userInvitations).set({
|
|
6843
7003
|
status: "cancelled",
|
|
6844
7004
|
cancelledAt: /* @__PURE__ */ new Date(),
|
|
6845
7005
|
metadata: newMetadata
|
|
6846
|
-
}).where(
|
|
7006
|
+
}).where(eq10(userInvitations.id, id14)).returning();
|
|
6847
7007
|
return result[0] ?? null;
|
|
6848
7008
|
}
|
|
6849
7009
|
};
|
|
@@ -7055,6 +7215,33 @@ var init_schema5 = __esm({
|
|
|
7055
7215
|
})
|
|
7056
7216
|
},
|
|
7057
7217
|
// ============================================================================
|
|
7218
|
+
// Verified-email signup
|
|
7219
|
+
// ============================================================================
|
|
7220
|
+
SPFN_AUTH_SIGNUP_LINK_TTL_MINUTES: {
|
|
7221
|
+
...envNumber({
|
|
7222
|
+
description: "How long an emailed signup confirmation link stays valid. Long enough to survive a mail delay, short enough that a link left in an inbox stops working.",
|
|
7223
|
+
default: 30,
|
|
7224
|
+
required: false,
|
|
7225
|
+
examples: [15, 30, 60]
|
|
7226
|
+
})
|
|
7227
|
+
},
|
|
7228
|
+
SPFN_AUTH_SIGNUP_SETUP_TTL_MINUTES: {
|
|
7229
|
+
...envNumber({
|
|
7230
|
+
description: "How long the password-setup session opened by a confirmation link stays valid. Covers one sitting at the password form, not an abandoned tab.",
|
|
7231
|
+
default: 15,
|
|
7232
|
+
required: false,
|
|
7233
|
+
examples: [10, 15, 30]
|
|
7234
|
+
})
|
|
7235
|
+
},
|
|
7236
|
+
SPFN_AUTH_SIGNUP_CONFIRM_PATH: {
|
|
7237
|
+
...envString({
|
|
7238
|
+
description: "App page the emailed confirmation link opens, as a path on {NEXT_PUBLIC_SPFN_APP_URL || SPFN_APP_URL}. The page reads the token from the query string and posts it to /_auth/signup/email/confirm; it is a page in your app, not an API route.",
|
|
7239
|
+
default: "/signup/confirm",
|
|
7240
|
+
required: false,
|
|
7241
|
+
examples: ["/signup/confirm", "/auth/confirm", "/join/verify"]
|
|
7242
|
+
})
|
|
7243
|
+
},
|
|
7244
|
+
// ============================================================================
|
|
7058
7245
|
// API Configuration
|
|
7059
7246
|
// ============================================================================
|
|
7060
7247
|
SPFN_API_URL: {
|
|
@@ -7463,15 +7650,15 @@ var init_token_cipher = __esm({
|
|
|
7463
7650
|
});
|
|
7464
7651
|
|
|
7465
7652
|
// src/server/repositories/social-accounts.repository.ts
|
|
7466
|
-
import { eq as
|
|
7467
|
-
import { BaseRepository as
|
|
7653
|
+
import { eq as eq11, and as and8 } from "drizzle-orm";
|
|
7654
|
+
import { BaseRepository as BaseRepository11 } from "@spfn/core/db";
|
|
7468
7655
|
var SocialAccountsRepository, socialAccountsRepository;
|
|
7469
7656
|
var init_social_accounts_repository = __esm({
|
|
7470
7657
|
"src/server/repositories/social-accounts.repository.ts"() {
|
|
7471
7658
|
"use strict";
|
|
7472
7659
|
init_entities();
|
|
7473
7660
|
init_token_cipher();
|
|
7474
|
-
SocialAccountsRepository = class extends
|
|
7661
|
+
SocialAccountsRepository = class extends BaseRepository11 {
|
|
7475
7662
|
/**
|
|
7476
7663
|
* 저장 row 의 토큰을 평문으로 복호화해 반환한다.
|
|
7477
7664
|
*
|
|
@@ -7499,10 +7686,10 @@ var init_social_accounts_repository = __esm({
|
|
|
7499
7686
|
if (refresh?.needsRotation) {
|
|
7500
7687
|
heal.refreshToken = await encryptToken(refresh.value, context("refresh"));
|
|
7501
7688
|
}
|
|
7502
|
-
await this.db.update(userSocialAccounts).set(heal).where(
|
|
7503
|
-
|
|
7504
|
-
access?.needsRotation && account.accessToken !== null ?
|
|
7505
|
-
refresh?.needsRotation && account.refreshToken !== null ?
|
|
7689
|
+
await this.db.update(userSocialAccounts).set(heal).where(and8(
|
|
7690
|
+
eq11(userSocialAccounts.id, account.id),
|
|
7691
|
+
access?.needsRotation && account.accessToken !== null ? eq11(userSocialAccounts.accessToken, account.accessToken) : void 0,
|
|
7692
|
+
refresh?.needsRotation && account.refreshToken !== null ? eq11(userSocialAccounts.refreshToken, account.refreshToken) : void 0
|
|
7506
7693
|
));
|
|
7507
7694
|
} catch {
|
|
7508
7695
|
}
|
|
@@ -7519,9 +7706,9 @@ var init_social_accounts_repository = __esm({
|
|
|
7519
7706
|
*/
|
|
7520
7707
|
async findByProviderAndProviderId(provider, providerUserId) {
|
|
7521
7708
|
const result = await this.readDb.select().from(userSocialAccounts).where(
|
|
7522
|
-
|
|
7523
|
-
|
|
7524
|
-
|
|
7709
|
+
and8(
|
|
7710
|
+
eq11(userSocialAccounts.provider, provider),
|
|
7711
|
+
eq11(userSocialAccounts.providerUserId, providerUserId)
|
|
7525
7712
|
)
|
|
7526
7713
|
).limit(1);
|
|
7527
7714
|
return this.decryptAccount(result[0] ?? null);
|
|
@@ -7531,7 +7718,7 @@ var init_social_accounts_repository = __esm({
|
|
|
7531
7718
|
* Read replica 사용
|
|
7532
7719
|
*/
|
|
7533
7720
|
async findByUserId(userId) {
|
|
7534
|
-
const result = await this.readDb.select().from(userSocialAccounts).where(
|
|
7721
|
+
const result = await this.readDb.select().from(userSocialAccounts).where(eq11(userSocialAccounts.userId, userId));
|
|
7535
7722
|
return Promise.all(result.map((account) => this.decryptAccount(account)));
|
|
7536
7723
|
}
|
|
7537
7724
|
/**
|
|
@@ -7540,9 +7727,9 @@ var init_social_accounts_repository = __esm({
|
|
|
7540
7727
|
*/
|
|
7541
7728
|
async findByUserIdAndProvider(userId, provider) {
|
|
7542
7729
|
const result = await this.readDb.select().from(userSocialAccounts).where(
|
|
7543
|
-
|
|
7544
|
-
|
|
7545
|
-
|
|
7730
|
+
and8(
|
|
7731
|
+
eq11(userSocialAccounts.userId, userId),
|
|
7732
|
+
eq11(userSocialAccounts.provider, provider)
|
|
7546
7733
|
)
|
|
7547
7734
|
).limit(1);
|
|
7548
7735
|
return this.decryptAccount(result[0] ?? null);
|
|
@@ -7568,11 +7755,11 @@ var init_social_accounts_repository = __esm({
|
|
|
7568
7755
|
* 토큰 정보 업데이트
|
|
7569
7756
|
* Write primary 사용
|
|
7570
7757
|
*/
|
|
7571
|
-
async updateTokens(
|
|
7758
|
+
async updateTokens(id14, data) {
|
|
7572
7759
|
const accounts = await this.db.select({
|
|
7573
7760
|
provider: userSocialAccounts.provider,
|
|
7574
7761
|
providerUserId: userSocialAccounts.providerUserId
|
|
7575
|
-
}).from(userSocialAccounts).where(
|
|
7762
|
+
}).from(userSocialAccounts).where(eq11(userSocialAccounts.id, id14)).limit(1);
|
|
7576
7763
|
const account = accounts[0];
|
|
7577
7764
|
if (!account) {
|
|
7578
7765
|
return null;
|
|
@@ -7586,15 +7773,15 @@ var init_social_accounts_repository = __esm({
|
|
|
7586
7773
|
...data,
|
|
7587
7774
|
accessToken: data.accessToken ? await encryptToken(data.accessToken, context("access")) : data.accessToken,
|
|
7588
7775
|
refreshToken: data.refreshToken ? await encryptToken(data.refreshToken, context("refresh")) : data.refreshToken
|
|
7589
|
-
}).where(
|
|
7776
|
+
}).where(eq11(userSocialAccounts.id, id14)).returning();
|
|
7590
7777
|
return this.decryptAccount(result[0] ?? null);
|
|
7591
7778
|
}
|
|
7592
7779
|
/**
|
|
7593
7780
|
* 소셜 계정 삭제
|
|
7594
7781
|
* Write primary 사용
|
|
7595
7782
|
*/
|
|
7596
|
-
async deleteById(
|
|
7597
|
-
const result = await this.db.delete(userSocialAccounts).where(
|
|
7783
|
+
async deleteById(id14) {
|
|
7784
|
+
const result = await this.db.delete(userSocialAccounts).where(eq11(userSocialAccounts.id, id14)).returning();
|
|
7598
7785
|
return result[0] ?? null;
|
|
7599
7786
|
}
|
|
7600
7787
|
/**
|
|
@@ -7603,9 +7790,9 @@ var init_social_accounts_repository = __esm({
|
|
|
7603
7790
|
*/
|
|
7604
7791
|
async deleteByUserIdAndProvider(userId, provider) {
|
|
7605
7792
|
const result = await this.db.delete(userSocialAccounts).where(
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7793
|
+
and8(
|
|
7794
|
+
eq11(userSocialAccounts.userId, userId),
|
|
7795
|
+
eq11(userSocialAccounts.provider, provider)
|
|
7609
7796
|
)
|
|
7610
7797
|
).returning();
|
|
7611
7798
|
return result[0] ?? null;
|
|
@@ -7618,7 +7805,7 @@ var init_social_accounts_repository = __esm({
|
|
|
7618
7805
|
* Write primary 사용
|
|
7619
7806
|
*/
|
|
7620
7807
|
async deleteAllByUserId(userId) {
|
|
7621
|
-
const result = await this.db.delete(userSocialAccounts).where(
|
|
7808
|
+
const result = await this.db.delete(userSocialAccounts).where(eq11(userSocialAccounts.userId, userId)).returning();
|
|
7622
7809
|
return result.length;
|
|
7623
7810
|
}
|
|
7624
7811
|
};
|
|
@@ -7627,19 +7814,19 @@ var init_social_accounts_repository = __esm({
|
|
|
7627
7814
|
});
|
|
7628
7815
|
|
|
7629
7816
|
// src/server/repositories/auth-metadata.repository.ts
|
|
7630
|
-
import { BaseRepository as
|
|
7631
|
-
import { eq as
|
|
7817
|
+
import { BaseRepository as BaseRepository12 } from "@spfn/core/db";
|
|
7818
|
+
import { eq as eq12 } from "drizzle-orm";
|
|
7632
7819
|
var AuthMetadataRepository, authMetadataRepository;
|
|
7633
7820
|
var init_auth_metadata_repository = __esm({
|
|
7634
7821
|
"src/server/repositories/auth-metadata.repository.ts"() {
|
|
7635
7822
|
"use strict";
|
|
7636
7823
|
init_auth_metadata();
|
|
7637
|
-
AuthMetadataRepository = class extends
|
|
7824
|
+
AuthMetadataRepository = class extends BaseRepository12 {
|
|
7638
7825
|
/**
|
|
7639
7826
|
* 키로 값 조회
|
|
7640
7827
|
*/
|
|
7641
7828
|
async get(key) {
|
|
7642
|
-
const result = await this.readDb.select().from(authMetadata).where(
|
|
7829
|
+
const result = await this.readDb.select().from(authMetadata).where(eq12(authMetadata.key, key)).limit(1);
|
|
7643
7830
|
return result[0]?.value ?? null;
|
|
7644
7831
|
}
|
|
7645
7832
|
/**
|
|
@@ -7662,20 +7849,20 @@ var init_auth_metadata_repository = __esm({
|
|
|
7662
7849
|
});
|
|
7663
7850
|
|
|
7664
7851
|
// src/server/repositories/account-deletion-requests.repository.ts
|
|
7665
|
-
import { eq as
|
|
7666
|
-
import { BaseRepository as
|
|
7852
|
+
import { eq as eq13, and as and9, lte } from "drizzle-orm";
|
|
7853
|
+
import { BaseRepository as BaseRepository13 } from "@spfn/core/db";
|
|
7667
7854
|
var AccountDeletionRequestsRepository, accountDeletionRequestsRepository;
|
|
7668
7855
|
var init_account_deletion_requests_repository = __esm({
|
|
7669
7856
|
"src/server/repositories/account-deletion-requests.repository.ts"() {
|
|
7670
7857
|
"use strict";
|
|
7671
7858
|
init_account_deletion_requests();
|
|
7672
|
-
AccountDeletionRequestsRepository = class extends
|
|
7859
|
+
AccountDeletionRequestsRepository = class extends BaseRepository13 {
|
|
7673
7860
|
/**
|
|
7674
7861
|
* ID로 요청 조회
|
|
7675
7862
|
* Read replica 사용
|
|
7676
7863
|
*/
|
|
7677
|
-
async findById(
|
|
7678
|
-
const result = await this.readDb.select().from(accountDeletionRequests).where(
|
|
7864
|
+
async findById(id14) {
|
|
7865
|
+
const result = await this.readDb.select().from(accountDeletionRequests).where(eq13(accountDeletionRequests.id, id14)).limit(1);
|
|
7679
7866
|
return result[0] ?? null;
|
|
7680
7867
|
}
|
|
7681
7868
|
/**
|
|
@@ -7684,9 +7871,9 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7684
7871
|
*/
|
|
7685
7872
|
async findPendingByUserId(userId) {
|
|
7686
7873
|
const result = await this.readDb.select().from(accountDeletionRequests).where(
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7874
|
+
and9(
|
|
7875
|
+
eq13(accountDeletionRequests.userId, userId),
|
|
7876
|
+
eq13(accountDeletionRequests.status, "pending")
|
|
7690
7877
|
)
|
|
7691
7878
|
).limit(1);
|
|
7692
7879
|
return result[0] ?? null;
|
|
@@ -7700,9 +7887,9 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7700
7887
|
*/
|
|
7701
7888
|
async findPendingByUserIdOnPrimary(userId) {
|
|
7702
7889
|
const result = await this.db.select().from(accountDeletionRequests).where(
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7890
|
+
and9(
|
|
7891
|
+
eq13(accountDeletionRequests.userId, userId),
|
|
7892
|
+
eq13(accountDeletionRequests.status, "pending")
|
|
7706
7893
|
)
|
|
7707
7894
|
).limit(1);
|
|
7708
7895
|
return result[0] ?? null;
|
|
@@ -7713,8 +7900,8 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7713
7900
|
*/
|
|
7714
7901
|
async findDueForPurge(now) {
|
|
7715
7902
|
return this.readDb.select().from(accountDeletionRequests).where(
|
|
7716
|
-
|
|
7717
|
-
|
|
7903
|
+
and9(
|
|
7904
|
+
eq13(accountDeletionRequests.status, "pending"),
|
|
7718
7905
|
lte(accountDeletionRequests.purgeScheduledAt, now)
|
|
7719
7906
|
)
|
|
7720
7907
|
);
|
|
@@ -7734,14 +7921,14 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7734
7921
|
* cancelled) 시 null을 반환하니 호출자가 그 결과를 확인해야 한다.
|
|
7735
7922
|
* Write primary 사용
|
|
7736
7923
|
*/
|
|
7737
|
-
async markCancelled(
|
|
7924
|
+
async markCancelled(id14) {
|
|
7738
7925
|
const result = await this.db.update(accountDeletionRequests).set({
|
|
7739
7926
|
status: "cancelled",
|
|
7740
7927
|
cancelledAt: /* @__PURE__ */ new Date()
|
|
7741
7928
|
}).where(
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7929
|
+
and9(
|
|
7930
|
+
eq13(accountDeletionRequests.id, id14),
|
|
7931
|
+
eq13(accountDeletionRequests.status, "pending")
|
|
7745
7932
|
)
|
|
7746
7933
|
).returning();
|
|
7747
7934
|
return result[0] ?? null;
|
|
@@ -7756,15 +7943,15 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7756
7943
|
* destructive DML을 실행하기 **전에** 반드시 이 결과를 확인해야 한다.
|
|
7757
7944
|
* Write primary 사용
|
|
7758
7945
|
*/
|
|
7759
|
-
async markCompleted(
|
|
7946
|
+
async markCompleted(id14, purgeStrategy) {
|
|
7760
7947
|
const result = await this.db.update(accountDeletionRequests).set({
|
|
7761
7948
|
status: "completed",
|
|
7762
7949
|
completedAt: /* @__PURE__ */ new Date(),
|
|
7763
7950
|
purgeStrategy
|
|
7764
7951
|
}).where(
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7952
|
+
and9(
|
|
7953
|
+
eq13(accountDeletionRequests.id, id14),
|
|
7954
|
+
eq13(accountDeletionRequests.status, "pending")
|
|
7768
7955
|
)
|
|
7769
7956
|
).returning();
|
|
7770
7957
|
return result[0] ?? null;
|
|
@@ -7775,14 +7962,14 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7775
7962
|
});
|
|
7776
7963
|
|
|
7777
7964
|
// src/server/repositories/ops-tokens.repository.ts
|
|
7778
|
-
import { and as
|
|
7779
|
-
import { BaseRepository as
|
|
7965
|
+
import { and as and10, desc as desc3, eq as eq14, isNull as isNull5 } from "drizzle-orm";
|
|
7966
|
+
import { BaseRepository as BaseRepository14 } from "@spfn/core/db";
|
|
7780
7967
|
var OpsTokensRepository, opsTokensRepository;
|
|
7781
7968
|
var init_ops_tokens_repository = __esm({
|
|
7782
7969
|
"src/server/repositories/ops-tokens.repository.ts"() {
|
|
7783
7970
|
"use strict";
|
|
7784
7971
|
init_ops_tokens();
|
|
7785
|
-
OpsTokensRepository = class extends
|
|
7972
|
+
OpsTokensRepository = class extends BaseRepository14 {
|
|
7786
7973
|
/**
|
|
7787
7974
|
* Lookup by the secret's hash — the verification path.
|
|
7788
7975
|
*
|
|
@@ -7792,7 +7979,7 @@ var init_ops_tokens_repository = __esm({
|
|
|
7792
7979
|
* and revocation is documented as taking effect immediately.
|
|
7793
7980
|
*/
|
|
7794
7981
|
async findByTokenHash(tokenHash) {
|
|
7795
|
-
const result = await this.db.select().from(opsTokens).where(
|
|
7982
|
+
const result = await this.db.select().from(opsTokens).where(eq14(opsTokens.tokenHash, tokenHash)).limit(1);
|
|
7796
7983
|
return result[0] ?? null;
|
|
7797
7984
|
}
|
|
7798
7985
|
async create(data) {
|
|
@@ -7807,13 +7994,13 @@ var init_ops_tokens_repository = __esm({
|
|
|
7807
7994
|
* token is already revoked — the first revocation's timestamp is never
|
|
7808
7995
|
* overwritten.
|
|
7809
7996
|
*/
|
|
7810
|
-
async revokeById(
|
|
7811
|
-
const result = await this.db.update(opsTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(
|
|
7997
|
+
async revokeById(id14) {
|
|
7998
|
+
const result = await this.db.update(opsTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and10(eq14(opsTokens.id, id14), isNull5(opsTokens.revokedAt))).returning();
|
|
7812
7999
|
return result[0] ?? null;
|
|
7813
8000
|
}
|
|
7814
8001
|
/** Fire-and-forget from the verification path. */
|
|
7815
|
-
async updateLastUsedById(
|
|
7816
|
-
await this.db.update(opsTokens).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(
|
|
8002
|
+
async updateLastUsedById(id14) {
|
|
8003
|
+
await this.db.update(opsTokens).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(eq14(opsTokens.id, id14));
|
|
7817
8004
|
}
|
|
7818
8005
|
};
|
|
7819
8006
|
opsTokensRepository = new OpsTokensRepository();
|
|
@@ -7827,6 +8014,7 @@ var init_repositories = __esm({
|
|
|
7827
8014
|
init_users_repository();
|
|
7828
8015
|
init_keys_repository();
|
|
7829
8016
|
init_verification_codes_repository();
|
|
8017
|
+
init_signup_link_tokens_repository();
|
|
7830
8018
|
init_roles_repository();
|
|
7831
8019
|
init_permissions_repository();
|
|
7832
8020
|
init_role_permissions_repository();
|
|
@@ -7928,7 +8116,7 @@ async function removePermissionFromRole(roleId, permissionId) {
|
|
|
7928
8116
|
}
|
|
7929
8117
|
async function setRolePermissions(roleId, permissionIds) {
|
|
7930
8118
|
const roleIdNum = Number(roleId);
|
|
7931
|
-
const permissionIdNums = permissionIds.map((
|
|
8119
|
+
const permissionIdNums = permissionIds.map((id14) => Number(id14));
|
|
7932
8120
|
await rolePermissionsRepository.setPermissionsForRole(roleIdNum, permissionIdNums);
|
|
7933
8121
|
}
|
|
7934
8122
|
async function getAllRoles(includeInactive = false) {
|
|
@@ -7948,7 +8136,7 @@ async function getRolePermissions(roleId) {
|
|
|
7948
8136
|
}
|
|
7949
8137
|
const permissionIds = mappings.map((m) => m.permissionId);
|
|
7950
8138
|
const perms = await Promise.all(
|
|
7951
|
-
permissionIds.map((
|
|
8139
|
+
permissionIds.map((id14) => permissionsRepository.findById(id14))
|
|
7952
8140
|
);
|
|
7953
8141
|
return perms.filter((p) => p !== null).map((p) => p.name);
|
|
7954
8142
|
}
|
|
@@ -8147,6 +8335,7 @@ function getKeyId(c) {
|
|
|
8147
8335
|
// src/server/routes/auth/index.ts
|
|
8148
8336
|
init_types();
|
|
8149
8337
|
import { KeyNotFoundError } from "@spfn/auth/errors";
|
|
8338
|
+
import { ValidationError as ValidationError9 } from "@spfn/core/errors";
|
|
8150
8339
|
|
|
8151
8340
|
// src/server/services/auth.service.ts
|
|
8152
8341
|
init_repositories();
|
|
@@ -8184,6 +8373,10 @@ var COOKIE_NAMES = {
|
|
|
8184
8373
|
/** OAuth CSRF nonce — double-submit against the (encrypted) state.nonce at callback */
|
|
8185
8374
|
get OAUTH_CSRF() {
|
|
8186
8375
|
return `spfn_oauth_csrf${getCookieSuffix()}`;
|
|
8376
|
+
},
|
|
8377
|
+
/** Password-setup session for verified-email signup — temporary, single-purpose */
|
|
8378
|
+
get SIGNUP_SETUP() {
|
|
8379
|
+
return `spfn_signup_setup${getCookieSuffix()}`;
|
|
8187
8380
|
}
|
|
8188
8381
|
};
|
|
8189
8382
|
function matchOAuthCsrfCookies(cookies) {
|
|
@@ -8387,24 +8580,28 @@ async function sendAccountExistsNotice(target, targetType) {
|
|
|
8387
8580
|
log.error("Failed to send account-exists notice", { target, error: result.error });
|
|
8388
8581
|
}
|
|
8389
8582
|
}
|
|
8583
|
+
async function noticeAccountExistsOnce(target, targetType) {
|
|
8584
|
+
const recentNotice = await verificationCodesRepository.findValidByTargetAndPurpose(target, "registration");
|
|
8585
|
+
if (recentNotice) {
|
|
8586
|
+
return;
|
|
8587
|
+
}
|
|
8588
|
+
const dedupeExpiresAt = new Date(Date.now() + ACCOUNT_EXISTS_NOTICE_DEDUPE_MINUTES * 6e4);
|
|
8589
|
+
await verificationCodesRepository.invalidatePreviousCodes(target, "registration");
|
|
8590
|
+
await verificationCodesRepository.create({
|
|
8591
|
+
target,
|
|
8592
|
+
targetType,
|
|
8593
|
+
code: generateVerificationCode(),
|
|
8594
|
+
purpose: "registration",
|
|
8595
|
+
expiresAt: dedupeExpiresAt,
|
|
8596
|
+
attempts: 0
|
|
8597
|
+
});
|
|
8598
|
+
await sendAccountExistsNotice(target, targetType);
|
|
8599
|
+
}
|
|
8390
8600
|
async function sendVerificationCodeService(params) {
|
|
8391
8601
|
const { targetType, purpose } = params;
|
|
8392
8602
|
const target = normalizeVerificationTarget(params.target, targetType);
|
|
8393
8603
|
if (purpose === "registration" && await accountExistsForTarget(target, targetType)) {
|
|
8394
|
-
|
|
8395
|
-
if (!recentNotice) {
|
|
8396
|
-
const dedupeExpiresAt = new Date(Date.now() + ACCOUNT_EXISTS_NOTICE_DEDUPE_MINUTES * 6e4);
|
|
8397
|
-
await verificationCodesRepository.invalidatePreviousCodes(target, purpose);
|
|
8398
|
-
await verificationCodesRepository.create({
|
|
8399
|
-
target,
|
|
8400
|
-
targetType,
|
|
8401
|
-
code: generateVerificationCode(),
|
|
8402
|
-
purpose,
|
|
8403
|
-
expiresAt: dedupeExpiresAt,
|
|
8404
|
-
attempts: 0
|
|
8405
|
-
});
|
|
8406
|
-
await sendAccountExistsNotice(target, targetType);
|
|
8407
|
-
}
|
|
8604
|
+
await noticeAccountExistsOnce(target, targetType);
|
|
8408
8605
|
return {
|
|
8409
8606
|
success: true,
|
|
8410
8607
|
expiresAt: new Date(Date.now() + VERIFICATION_CODE_EXPIRY_MINUTES * 6e4).toISOString()
|
|
@@ -8774,8 +8971,8 @@ async function verifyReauthCredential(user, params) {
|
|
|
8774
8971
|
throw new VerificationTokenTargetMismatchError();
|
|
8775
8972
|
}
|
|
8776
8973
|
}
|
|
8777
|
-
async function sendDeletionEmail(to, subject,
|
|
8778
|
-
const result = await sendEmail2({ to, subject, text:
|
|
8974
|
+
async function sendDeletionEmail(to, subject, text14) {
|
|
8975
|
+
const result = await sendEmail2({ to, subject, text: text14 });
|
|
8779
8976
|
if (!result.success) {
|
|
8780
8977
|
authLogger.email.error("Failed to send account deletion email", { to, subject, error: result.error });
|
|
8781
8978
|
}
|
|
@@ -9022,7 +9219,7 @@ async function sweepDuePurges(now = /* @__PURE__ */ new Date()) {
|
|
|
9022
9219
|
|
|
9023
9220
|
// src/server/services/auth.service.ts
|
|
9024
9221
|
async function registerService(params) {
|
|
9025
|
-
const { email, verificationToken
|
|
9222
|
+
const { email, verificationToken } = params;
|
|
9026
9223
|
const phone = params.phone?.trim();
|
|
9027
9224
|
const tokenPayload = validateVerificationToken(verificationToken);
|
|
9028
9225
|
if (!tokenPayload) {
|
|
@@ -9039,6 +9236,13 @@ async function registerService(params) {
|
|
|
9039
9236
|
if (tokenPayload.targetType !== providedTargetType) {
|
|
9040
9237
|
throw new VerificationTokenTargetMismatchError2();
|
|
9041
9238
|
}
|
|
9239
|
+
return await createVerifiedAccount({ ...params, phone });
|
|
9240
|
+
}
|
|
9241
|
+
async function createVerifiedAccount(params) {
|
|
9242
|
+
const { email, phone, password, publicKey, keyId, fingerprint, algorithm, metadata } = params;
|
|
9243
|
+
if (!publicKey || !keyId || !fingerprint) {
|
|
9244
|
+
throw new ValidationError3({ message: "Device key material is required to register" });
|
|
9245
|
+
}
|
|
9042
9246
|
const existingUser = await usersRepository.findByEmailOrPhone(email, phone);
|
|
9043
9247
|
if (existingUser) {
|
|
9044
9248
|
const identifierType = email ? "email" : "phone";
|
|
@@ -9170,6 +9374,158 @@ async function changePasswordService(params) {
|
|
|
9170
9374
|
await keysRepository.revokeAllActiveByUserId(userId, "Revoked by password change");
|
|
9171
9375
|
}
|
|
9172
9376
|
|
|
9377
|
+
// src/server/services/signup-link.service.ts
|
|
9378
|
+
import crypto5 from "crypto";
|
|
9379
|
+
import { env as env7 } from "@spfn/auth/config";
|
|
9380
|
+
import { InvalidSignupLinkError, InvalidSignupSetupSessionError } from "@spfn/auth/errors";
|
|
9381
|
+
import { sendEmail as sendEmail3 } from "@spfn/notification/server";
|
|
9382
|
+
init_repositories();
|
|
9383
|
+
var CREDENTIAL_BYTES = 32;
|
|
9384
|
+
function mintCredential() {
|
|
9385
|
+
const secret = crypto5.randomBytes(CREDENTIAL_BYTES).toString("base64url");
|
|
9386
|
+
return { secret, hash: hashCredential(secret) };
|
|
9387
|
+
}
|
|
9388
|
+
function hashCredential(secret) {
|
|
9389
|
+
return crypto5.createHash("sha256").update(secret).digest("base64url");
|
|
9390
|
+
}
|
|
9391
|
+
function isSafeReturnPath(returnPath) {
|
|
9392
|
+
if (!returnPath.startsWith("/")) {
|
|
9393
|
+
return false;
|
|
9394
|
+
}
|
|
9395
|
+
if (returnPath.startsWith("//") || returnPath.includes("\\")) {
|
|
9396
|
+
return false;
|
|
9397
|
+
}
|
|
9398
|
+
if (returnPath.includes("..")) {
|
|
9399
|
+
return false;
|
|
9400
|
+
}
|
|
9401
|
+
return !/^\/[^/?#]*:/.test(returnPath);
|
|
9402
|
+
}
|
|
9403
|
+
function buildConfirmUrl(token) {
|
|
9404
|
+
const appUrl = (env7.NEXT_PUBLIC_SPFN_APP_URL || env7.SPFN_APP_URL || "").replace(/\/$/, "");
|
|
9405
|
+
const path = env7.SPFN_AUTH_SIGNUP_CONFIRM_PATH || "/signup/confirm";
|
|
9406
|
+
return `${appUrl}${path}?token=${encodeURIComponent(token)}`;
|
|
9407
|
+
}
|
|
9408
|
+
async function sendSignupLinkEmail(email, confirmUrl, expiresInMinutes) {
|
|
9409
|
+
const result = await sendEmail3({
|
|
9410
|
+
to: email,
|
|
9411
|
+
template: "signup-link",
|
|
9412
|
+
data: { confirmUrl, expiresInMinutes }
|
|
9413
|
+
});
|
|
9414
|
+
if (!result.success) {
|
|
9415
|
+
authLogger.email.error("Failed to send signup link email", {
|
|
9416
|
+
email,
|
|
9417
|
+
error: result.error
|
|
9418
|
+
});
|
|
9419
|
+
}
|
|
9420
|
+
}
|
|
9421
|
+
async function requestSignupLinkService(params) {
|
|
9422
|
+
const email = params.email.trim();
|
|
9423
|
+
const returnPath = params.returnPath;
|
|
9424
|
+
const ttlMinutes = env7.SPFN_AUTH_SIGNUP_LINK_TTL_MINUTES ?? 30;
|
|
9425
|
+
const expiresAt = new Date(Date.now() + ttlMinutes * 6e4);
|
|
9426
|
+
const existingUser = await usersRepository.findByEmail(email);
|
|
9427
|
+
if (existingUser) {
|
|
9428
|
+
await noticeAccountExistsOnce(email, "email");
|
|
9429
|
+
return { success: true, expiresAt: expiresAt.toISOString() };
|
|
9430
|
+
}
|
|
9431
|
+
await signupLinkTokensRepository.supersedeLiveForEmail(email);
|
|
9432
|
+
const { secret, hash: hash2 } = mintCredential();
|
|
9433
|
+
await signupLinkTokensRepository.create({
|
|
9434
|
+
email,
|
|
9435
|
+
tokenHash: hash2,
|
|
9436
|
+
returnPath: returnPath ?? null,
|
|
9437
|
+
expiresAt
|
|
9438
|
+
});
|
|
9439
|
+
await sendSignupLinkEmail(email, buildConfirmUrl(secret), ttlMinutes);
|
|
9440
|
+
return { success: true, expiresAt: expiresAt.toISOString() };
|
|
9441
|
+
}
|
|
9442
|
+
function linkRefusalReason(row) {
|
|
9443
|
+
if (!row) {
|
|
9444
|
+
return "unknown token";
|
|
9445
|
+
}
|
|
9446
|
+
if (row.completedAt) {
|
|
9447
|
+
return "signup already completed";
|
|
9448
|
+
}
|
|
9449
|
+
if (row.supersededAt) {
|
|
9450
|
+
return "superseded by a newer request";
|
|
9451
|
+
}
|
|
9452
|
+
if (row.consumedAt) {
|
|
9453
|
+
return "link already used";
|
|
9454
|
+
}
|
|
9455
|
+
if (/* @__PURE__ */ new Date() > new Date(row.expiresAt)) {
|
|
9456
|
+
return "link expired";
|
|
9457
|
+
}
|
|
9458
|
+
return null;
|
|
9459
|
+
}
|
|
9460
|
+
async function confirmSignupLinkService(params) {
|
|
9461
|
+
const row = await signupLinkTokensRepository.findByTokenHash(hashCredential(params.token));
|
|
9462
|
+
const refusal = linkRefusalReason(row);
|
|
9463
|
+
if (refusal || !row) {
|
|
9464
|
+
authLogger.service.warn("Signup link refused", { reason: refusal });
|
|
9465
|
+
throw new InvalidSignupLinkError();
|
|
9466
|
+
}
|
|
9467
|
+
if (await usersRepository.findByEmail(row.email)) {
|
|
9468
|
+
authLogger.service.warn("Signup link refused", { reason: "account created meanwhile" });
|
|
9469
|
+
throw new InvalidSignupLinkError();
|
|
9470
|
+
}
|
|
9471
|
+
const setupTtlMinutes = env7.SPFN_AUTH_SIGNUP_SETUP_TTL_MINUTES ?? 15;
|
|
9472
|
+
const setupExpiresAt = new Date(Date.now() + setupTtlMinutes * 6e4);
|
|
9473
|
+
const setup = mintCredential();
|
|
9474
|
+
const claimed = await signupLinkTokensRepository.claimLink(row.id, setup.hash, setupExpiresAt);
|
|
9475
|
+
if (!claimed) {
|
|
9476
|
+
authLogger.service.warn("Signup link refused", { reason: "lost the claim race" });
|
|
9477
|
+
throw new InvalidSignupLinkError();
|
|
9478
|
+
}
|
|
9479
|
+
return {
|
|
9480
|
+
email: claimed.email,
|
|
9481
|
+
returnPath: claimed.returnPath,
|
|
9482
|
+
setupSecret: setup.secret,
|
|
9483
|
+
setupExpiresAt: setupExpiresAt.toISOString()
|
|
9484
|
+
};
|
|
9485
|
+
}
|
|
9486
|
+
function setupRefusalReason(row) {
|
|
9487
|
+
if (!row) {
|
|
9488
|
+
return "unknown setup session";
|
|
9489
|
+
}
|
|
9490
|
+
if (row.completedAt) {
|
|
9491
|
+
return "setup session already used";
|
|
9492
|
+
}
|
|
9493
|
+
if (row.supersededAt) {
|
|
9494
|
+
return "superseded by a newer request";
|
|
9495
|
+
}
|
|
9496
|
+
if (!row.setupExpiresAt || /* @__PURE__ */ new Date() > new Date(row.setupExpiresAt)) {
|
|
9497
|
+
return "setup session expired";
|
|
9498
|
+
}
|
|
9499
|
+
return null;
|
|
9500
|
+
}
|
|
9501
|
+
async function completeSignupService(params) {
|
|
9502
|
+
if (!params.setupSecret) {
|
|
9503
|
+
throw new InvalidSignupSetupSessionError();
|
|
9504
|
+
}
|
|
9505
|
+
const row = await signupLinkTokensRepository.findBySetupSecretHash(hashCredential(params.setupSecret));
|
|
9506
|
+
const refusal = setupRefusalReason(row);
|
|
9507
|
+
if (refusal || !row) {
|
|
9508
|
+
authLogger.service.warn("Signup setup session refused", { reason: refusal });
|
|
9509
|
+
throw new InvalidSignupSetupSessionError();
|
|
9510
|
+
}
|
|
9511
|
+
const claimed = await signupLinkTokensRepository.claimSetupSession(row.id);
|
|
9512
|
+
if (!claimed) {
|
|
9513
|
+
authLogger.service.warn("Signup setup session refused", { reason: "lost the claim race" });
|
|
9514
|
+
throw new InvalidSignupSetupSessionError();
|
|
9515
|
+
}
|
|
9516
|
+
return await createVerifiedAccount({
|
|
9517
|
+
email: claimed.email,
|
|
9518
|
+
password: params.password,
|
|
9519
|
+
publicKey: params.publicKey,
|
|
9520
|
+
keyId: params.keyId,
|
|
9521
|
+
fingerprint: params.fingerprint,
|
|
9522
|
+
algorithm: params.algorithm,
|
|
9523
|
+
deviceName: params.deviceName,
|
|
9524
|
+
platform: params.platform,
|
|
9525
|
+
metadata: params.metadata
|
|
9526
|
+
});
|
|
9527
|
+
}
|
|
9528
|
+
|
|
9173
9529
|
// src/server/services/rbac.service.ts
|
|
9174
9530
|
init_repositories();
|
|
9175
9531
|
init_rbac();
|
|
@@ -9347,7 +9703,7 @@ async function getUserPermissions(userId) {
|
|
|
9347
9703
|
const permIds = rolePermMappings.map((rp) => rp.permissionId);
|
|
9348
9704
|
if (permIds.length > 0) {
|
|
9349
9705
|
const rolePerms = await Promise.all(
|
|
9350
|
-
permIds.map((
|
|
9706
|
+
permIds.map((id14) => permissionsRepository.findById(id14))
|
|
9351
9707
|
);
|
|
9352
9708
|
for (const perm of rolePerms) {
|
|
9353
9709
|
if (perm && perm.isActive) {
|
|
@@ -9422,10 +9778,10 @@ init_role_service();
|
|
|
9422
9778
|
|
|
9423
9779
|
// src/server/services/invitation.service.ts
|
|
9424
9780
|
init_repositories();
|
|
9425
|
-
import
|
|
9781
|
+
import crypto6 from "crypto";
|
|
9426
9782
|
import { BadRequestError, NotFoundError as NotFoundError3, ConflictError } from "@spfn/core/errors";
|
|
9427
9783
|
function generateInvitationToken() {
|
|
9428
|
-
return
|
|
9784
|
+
return crypto6.randomUUID();
|
|
9429
9785
|
}
|
|
9430
9786
|
function calculateExpiresAt(days = 7) {
|
|
9431
9787
|
const expiresAt = /* @__PURE__ */ new Date();
|
|
@@ -9560,20 +9916,20 @@ async function acceptInvitation(params) {
|
|
|
9560
9916
|
async function listInvitations(params) {
|
|
9561
9917
|
return await invitationsRepository.list(params);
|
|
9562
9918
|
}
|
|
9563
|
-
async function cancelInvitation(
|
|
9564
|
-
const invitation = await invitationsRepository.findById(
|
|
9919
|
+
async function cancelInvitation(id14, cancelledBy, reason) {
|
|
9920
|
+
const invitation = await invitationsRepository.findById(id14);
|
|
9565
9921
|
if (!invitation) {
|
|
9566
9922
|
throw new NotFoundError3({ message: "Invitation not found", resource: "Invitation" });
|
|
9567
9923
|
}
|
|
9568
9924
|
if (invitation.status !== "pending") {
|
|
9569
9925
|
throw new ConflictError({ message: `Cannot cancel ${invitation.status} invitation` });
|
|
9570
9926
|
}
|
|
9571
|
-
await invitationsRepository.cancel(
|
|
9927
|
+
await invitationsRepository.cancel(id14, cancelledBy, reason, invitation.metadata);
|
|
9572
9928
|
console.log(`[Auth] \u26A0\uFE0F Invitation cancelled: ${invitation.email} (reason: ${reason || "none"})`);
|
|
9573
9929
|
}
|
|
9574
|
-
async function deleteInvitation(
|
|
9575
|
-
await invitationsRepository.deleteById(
|
|
9576
|
-
console.log(`[Auth] \u{1F5D1}\uFE0F Invitation deleted: ${
|
|
9930
|
+
async function deleteInvitation(id14) {
|
|
9931
|
+
await invitationsRepository.deleteById(id14);
|
|
9932
|
+
console.log(`[Auth] \u{1F5D1}\uFE0F Invitation deleted: ${id14}`);
|
|
9577
9933
|
}
|
|
9578
9934
|
async function expireOldInvitations() {
|
|
9579
9935
|
const count = await invitationsRepository.updateExpiredInvitations();
|
|
@@ -9582,8 +9938,8 @@ async function expireOldInvitations() {
|
|
|
9582
9938
|
}
|
|
9583
9939
|
return count;
|
|
9584
9940
|
}
|
|
9585
|
-
async function resendInvitation(
|
|
9586
|
-
const invitation = await invitationsRepository.findById(
|
|
9941
|
+
async function resendInvitation(id14, expiresInDays = 7) {
|
|
9942
|
+
const invitation = await invitationsRepository.findById(id14);
|
|
9587
9943
|
if (!invitation) {
|
|
9588
9944
|
throw new NotFoundError3({ message: "Invitation not found", resource: "Invitation" });
|
|
9589
9945
|
}
|
|
@@ -9591,7 +9947,7 @@ async function resendInvitation(id13, expiresInDays = 7) {
|
|
|
9591
9947
|
throw new ConflictError({ message: `Cannot resend ${invitation.status} invitation` });
|
|
9592
9948
|
}
|
|
9593
9949
|
const newExpiresAt = calculateExpiresAt(expiresInDays);
|
|
9594
|
-
const updated = await invitationsRepository.resend(
|
|
9950
|
+
const updated = await invitationsRepository.resend(id14, newExpiresAt);
|
|
9595
9951
|
if (!updated) {
|
|
9596
9952
|
throw new Error("Failed to update invitation");
|
|
9597
9953
|
}
|
|
@@ -9745,7 +10101,7 @@ async function updateUserProfileService(userId, params) {
|
|
|
9745
10101
|
|
|
9746
10102
|
// src/server/services/oauth.service.ts
|
|
9747
10103
|
init_repositories();
|
|
9748
|
-
import { env as
|
|
10104
|
+
import { env as env12 } from "@spfn/auth/config";
|
|
9749
10105
|
import { ValidationError as ValidationError8 } from "@spfn/core/errors";
|
|
9750
10106
|
import {
|
|
9751
10107
|
AccountDisabledError as AccountDisabledError2,
|
|
@@ -9754,21 +10110,21 @@ import {
|
|
|
9754
10110
|
} from "@spfn/auth/errors";
|
|
9755
10111
|
|
|
9756
10112
|
// src/server/lib/oauth/google.ts
|
|
9757
|
-
import { env as
|
|
10113
|
+
import { env as env8 } from "@spfn/auth/config";
|
|
9758
10114
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
9759
10115
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
9760
10116
|
var GOOGLE_USERINFO_URL = "https://www.googleapis.com/oauth2/v2/userinfo";
|
|
9761
10117
|
function isGoogleOAuthEnabled() {
|
|
9762
|
-
return !!(
|
|
10118
|
+
return !!(env8.SPFN_AUTH_GOOGLE_CLIENT_ID && env8.SPFN_AUTH_GOOGLE_CLIENT_SECRET);
|
|
9763
10119
|
}
|
|
9764
10120
|
function getGoogleOAuthConfig() {
|
|
9765
|
-
const clientId =
|
|
9766
|
-
const clientSecret =
|
|
10121
|
+
const clientId = env8.SPFN_AUTH_GOOGLE_CLIENT_ID;
|
|
10122
|
+
const clientSecret = env8.SPFN_AUTH_GOOGLE_CLIENT_SECRET;
|
|
9767
10123
|
if (!clientId || !clientSecret) {
|
|
9768
10124
|
throw new Error("Google OAuth is not configured. Set SPFN_AUTH_GOOGLE_CLIENT_ID and SPFN_AUTH_GOOGLE_CLIENT_SECRET.");
|
|
9769
10125
|
}
|
|
9770
|
-
const baseUrl =
|
|
9771
|
-
const redirectUri =
|
|
10126
|
+
const baseUrl = env8.NEXT_PUBLIC_SPFN_APP_URL || env8.SPFN_APP_URL;
|
|
10127
|
+
const redirectUri = env8.SPFN_AUTH_GOOGLE_REDIRECT_URI || `${baseUrl}/_auth/oauth/google/callback`;
|
|
9772
10128
|
return {
|
|
9773
10129
|
clientId,
|
|
9774
10130
|
clientSecret,
|
|
@@ -9776,7 +10132,7 @@ function getGoogleOAuthConfig() {
|
|
|
9776
10132
|
};
|
|
9777
10133
|
}
|
|
9778
10134
|
function getDefaultScopes() {
|
|
9779
|
-
const envScopes =
|
|
10135
|
+
const envScopes = env8.SPFN_AUTH_GOOGLE_SCOPES;
|
|
9780
10136
|
if (envScopes) {
|
|
9781
10137
|
return envScopes.split(",").map((s) => s.trim()).filter(Boolean);
|
|
9782
10138
|
}
|
|
@@ -9854,9 +10210,9 @@ async function refreshAccessToken(refreshToken) {
|
|
|
9854
10210
|
|
|
9855
10211
|
// src/server/lib/oauth/state.ts
|
|
9856
10212
|
import * as jose from "jose";
|
|
9857
|
-
import { env as
|
|
10213
|
+
import { env as env9 } from "@spfn/auth/config";
|
|
9858
10214
|
async function getStateKey() {
|
|
9859
|
-
const secret =
|
|
10215
|
+
const secret = env9.SPFN_AUTH_SESSION_SECRET;
|
|
9860
10216
|
const encoder = new TextEncoder();
|
|
9861
10217
|
const data = encoder.encode(`oauth-state:${secret}`);
|
|
9862
10218
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
@@ -9905,8 +10261,8 @@ var registry2 = /* @__PURE__ */ new Map();
|
|
|
9905
10261
|
function registerOAuthProvider(provider) {
|
|
9906
10262
|
registry2.set(provider.id, provider);
|
|
9907
10263
|
}
|
|
9908
|
-
function getOAuthProvider(
|
|
9909
|
-
return registry2.get(
|
|
10264
|
+
function getOAuthProvider(id14) {
|
|
10265
|
+
return registry2.get(id14);
|
|
9910
10266
|
}
|
|
9911
10267
|
function getRegisteredProviders() {
|
|
9912
10268
|
return [...registry2.values()];
|
|
@@ -9959,14 +10315,14 @@ async function verifySignature(params) {
|
|
|
9959
10315
|
init_token_cipher();
|
|
9960
10316
|
|
|
9961
10317
|
// src/server/lib/oauth/google-provider.ts
|
|
9962
|
-
import { env as
|
|
10318
|
+
import { env as env10 } from "@spfn/auth/config";
|
|
9963
10319
|
import { NativeSignInUnsupportedError } from "@spfn/auth/errors";
|
|
9964
10320
|
var GOOGLE_JWKS_URI = "https://www.googleapis.com/oauth2/v3/certs";
|
|
9965
10321
|
var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
|
|
9966
10322
|
function getGoogleNativeAudiences() {
|
|
9967
|
-
const ids = (
|
|
9968
|
-
if (
|
|
9969
|
-
ids.push(
|
|
10323
|
+
const ids = (env10.SPFN_AUTH_GOOGLE_NATIVE_CLIENT_IDS || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
10324
|
+
if (env10.SPFN_AUTH_GOOGLE_CLIENT_ID) {
|
|
10325
|
+
ids.push(env10.SPFN_AUTH_GOOGLE_CLIENT_ID);
|
|
9970
10326
|
}
|
|
9971
10327
|
return ids;
|
|
9972
10328
|
}
|
|
@@ -10028,13 +10384,13 @@ registerOAuthProvider(googleProvider);
|
|
|
10028
10384
|
|
|
10029
10385
|
// src/server/lib/oauth/apple-provider.ts
|
|
10030
10386
|
import { createHash as createHash2 } from "crypto";
|
|
10031
|
-
import { env as
|
|
10387
|
+
import { env as env11 } from "@spfn/auth/config";
|
|
10032
10388
|
import { ValidationError as ValidationError4 } from "@spfn/core/errors";
|
|
10033
10389
|
import { NativeSignInUnsupportedError as NativeSignInUnsupportedError2 } from "@spfn/auth/errors";
|
|
10034
10390
|
var APPLE_JWKS_URI = "https://appleid.apple.com/auth/keys";
|
|
10035
10391
|
var APPLE_ISSUER = "https://appleid.apple.com";
|
|
10036
10392
|
function getAppleClientIds() {
|
|
10037
|
-
return (
|
|
10393
|
+
return (env11.SPFN_AUTH_APPLE_CLIENT_IDS || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
10038
10394
|
}
|
|
10039
10395
|
function hashNonce(rawNonce) {
|
|
10040
10396
|
return createHash2("sha256").update(rawNonce).digest("hex");
|
|
@@ -10747,8 +11103,8 @@ async function oauthCallbackService(params) {
|
|
|
10747
11103
|
algorithm: stateData.algorithm
|
|
10748
11104
|
});
|
|
10749
11105
|
await updateLastLoginService(userId);
|
|
10750
|
-
const appUrl =
|
|
10751
|
-
const callbackPath =
|
|
11106
|
+
const appUrl = env12.NEXT_PUBLIC_SPFN_APP_URL || env12.SPFN_APP_URL;
|
|
11107
|
+
const callbackPath = env12.SPFN_AUTH_OAUTH_SUCCESS_URL || "/auth/callback";
|
|
10752
11108
|
const callbackUrl = callbackPath.startsWith("http") ? callbackPath : `${appUrl}${callbackPath}`;
|
|
10753
11109
|
const redirectUrl = buildRedirectUrl(callbackUrl, {
|
|
10754
11110
|
userId: String(userId),
|
|
@@ -10876,7 +11232,7 @@ function buildRedirectUrl(baseUrl, params) {
|
|
|
10876
11232
|
return `${url.pathname}${url.search}`;
|
|
10877
11233
|
}
|
|
10878
11234
|
function buildOAuthErrorUrl(error) {
|
|
10879
|
-
const errorUrl =
|
|
11235
|
+
const errorUrl = env12.SPFN_AUTH_OAUTH_ERROR_URL || "/auth/error?error={error}";
|
|
10880
11236
|
return errorUrl.replace("{error}", encodeURIComponent(error));
|
|
10881
11237
|
}
|
|
10882
11238
|
function isOAuthProviderEnabled(provider) {
|
|
@@ -11042,8 +11398,8 @@ async function verifyOpsTokenService(token) {
|
|
|
11042
11398
|
scopes: record.scopes
|
|
11043
11399
|
};
|
|
11044
11400
|
}
|
|
11045
|
-
async function revokeOpsTokenService(
|
|
11046
|
-
return await opsTokensRepository.revokeById(
|
|
11401
|
+
async function revokeOpsTokenService(id14) {
|
|
11402
|
+
return await opsTokensRepository.revokeById(id14);
|
|
11047
11403
|
}
|
|
11048
11404
|
async function listOpsTokensService() {
|
|
11049
11405
|
return await opsTokensRepository.list();
|
|
@@ -11179,6 +11535,54 @@ var register = route.post("/_auth/register").input({
|
|
|
11179
11535
|
const { body } = await c.data();
|
|
11180
11536
|
return await registerService(body);
|
|
11181
11537
|
});
|
|
11538
|
+
var requestSignupLink = route.post("/_auth/signup/email").input({
|
|
11539
|
+
body: Type.Object({
|
|
11540
|
+
email: EmailSchema,
|
|
11541
|
+
returnPath: Type.Optional(Type.String({
|
|
11542
|
+
maxLength: 512,
|
|
11543
|
+
description: "Relative path within the app to return to after signup. Absolute URLs are rejected."
|
|
11544
|
+
}))
|
|
11545
|
+
})
|
|
11546
|
+
}).use([rateLimitPolicy("auth-signup-link", { limit: 5, windowMs: 6e4, by: byIpAndAccount({ ipLimit: 20 }) })]).skip(["auth"]).handler(async (c) => {
|
|
11547
|
+
const { body } = await c.data();
|
|
11548
|
+
if (body.returnPath !== void 0 && !isSafeReturnPath(body.returnPath)) {
|
|
11549
|
+
throw new ValidationError9({ message: "returnPath must be a relative path within the app" });
|
|
11550
|
+
}
|
|
11551
|
+
return await requestSignupLinkService(body);
|
|
11552
|
+
});
|
|
11553
|
+
var confirmSignupLink = route.post("/_auth/signup/email/confirm").input({
|
|
11554
|
+
body: Type.Object({
|
|
11555
|
+
token: Type.String({
|
|
11556
|
+
minLength: 16,
|
|
11557
|
+
maxLength: 256,
|
|
11558
|
+
description: "Token from the confirmation link"
|
|
11559
|
+
})
|
|
11560
|
+
})
|
|
11561
|
+
}).use([rateLimitPolicy("auth-signup-confirm", { limit: 10, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
|
|
11562
|
+
const { body } = await c.data();
|
|
11563
|
+
return await confirmSignupLinkService(body);
|
|
11564
|
+
});
|
|
11565
|
+
var completeSignup = route.post("/_auth/signup/password").input({
|
|
11566
|
+
body: Type.Object({
|
|
11567
|
+
password: PasswordSchema,
|
|
11568
|
+
metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown(), {
|
|
11569
|
+
description: "Custom metadata passed to authRegisterEvent (e.g. referral code, UTM params)"
|
|
11570
|
+
}))
|
|
11571
|
+
})
|
|
11572
|
+
}).interceptor({
|
|
11573
|
+
body: Type.Object({
|
|
11574
|
+
setupSecret: Type.String({ description: "Password-setup session secret, from the HttpOnly cookie" }),
|
|
11575
|
+
publicKey: Type.String({ description: "Client public key" }),
|
|
11576
|
+
keyId: Type.String({ description: "Key identifier" }),
|
|
11577
|
+
fingerprint: Type.String({ description: "Key fingerprint" }),
|
|
11578
|
+
algorithm: Type.Union(KEY_ALGORITHM.map((algo) => Type.Literal(algo)), { description: "Signature algorithm" }),
|
|
11579
|
+
deviceName: Type.Optional(DeviceNameSchema),
|
|
11580
|
+
platform: Type.Optional(PlatformSchema)
|
|
11581
|
+
})
|
|
11582
|
+
}).use([rateLimitPolicy("auth-signup-password", { limit: 10, windowMs: 6e4 }), Transactional()]).skip(["auth"]).handler(async (c) => {
|
|
11583
|
+
const { body } = await c.data();
|
|
11584
|
+
return await completeSignupService(body);
|
|
11585
|
+
});
|
|
11182
11586
|
var login = route.post("/_auth/login").input({
|
|
11183
11587
|
body: Type.Object({
|
|
11184
11588
|
email: Type.Optional(EmailSchema),
|
|
@@ -11351,13 +11755,13 @@ var CanonicalJsonError = class extends Error {
|
|
|
11351
11755
|
var INT64_MIN = -(2n ** 63n);
|
|
11352
11756
|
var INT64_MAX = 2n ** 63n - 1n;
|
|
11353
11757
|
function parseCanonicalJson(bytes) {
|
|
11354
|
-
let
|
|
11758
|
+
let text14;
|
|
11355
11759
|
try {
|
|
11356
|
-
|
|
11760
|
+
text14 = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
11357
11761
|
} catch {
|
|
11358
11762
|
throw new CanonicalJsonError("INVALID_UTF8");
|
|
11359
11763
|
}
|
|
11360
|
-
const parser = new Parser(
|
|
11764
|
+
const parser = new Parser(text14);
|
|
11361
11765
|
const value = parser.parseValue();
|
|
11362
11766
|
parser.skipWhitespace();
|
|
11363
11767
|
if (!parser.atEnd()) {
|
|
@@ -11378,8 +11782,8 @@ function isCanonicalBytes(bytes, value) {
|
|
|
11378
11782
|
return true;
|
|
11379
11783
|
}
|
|
11380
11784
|
var Parser = class {
|
|
11381
|
-
constructor(
|
|
11382
|
-
this.text =
|
|
11785
|
+
constructor(text14) {
|
|
11786
|
+
this.text = text14;
|
|
11383
11787
|
}
|
|
11384
11788
|
pos = 0;
|
|
11385
11789
|
atEnd() {
|
|
@@ -13292,7 +13696,7 @@ var deleteCookie = (c, name, opt) => {
|
|
|
13292
13696
|
init_types();
|
|
13293
13697
|
init_schema3();
|
|
13294
13698
|
import { Transactional as Transactional3 } from "@spfn/core/db";
|
|
13295
|
-
import { ValidationError as
|
|
13699
|
+
import { ValidationError as ValidationError10 } from "@spfn/core/errors";
|
|
13296
13700
|
import { rateLimitPolicy as rateLimitPolicy4 } from "@spfn/core/middleware";
|
|
13297
13701
|
import { defineRouter as defineRouter4, route as route4 } from "@spfn/core/route";
|
|
13298
13702
|
var providerParams = Type.Object({
|
|
@@ -13412,10 +13816,10 @@ var getGoogleOAuthUrl = route4.post("/_auth/oauth/google/url").input({
|
|
|
13412
13816
|
}).use([rateLimitPolicy4("oauth-start", { limit: 20, windowMs: 6e4 })]).skip(["auth"]).handler(async (c) => {
|
|
13413
13817
|
const { body } = await c.data();
|
|
13414
13818
|
if (!isGoogleOAuthEnabled()) {
|
|
13415
|
-
throw new
|
|
13819
|
+
throw new ValidationError10({ message: "Google OAuth is not configured" });
|
|
13416
13820
|
}
|
|
13417
13821
|
if (!body.state) {
|
|
13418
|
-
throw new
|
|
13822
|
+
throw new ValidationError10({
|
|
13419
13823
|
message: "OAuth state is required. Ensure the OAuth interceptor is configured."
|
|
13420
13824
|
});
|
|
13421
13825
|
}
|
|
@@ -13510,7 +13914,7 @@ var getProviderOAuthUrl = route4.post("/_auth/oauth/:provider/url").input({
|
|
|
13510
13914
|
const { params, body } = await c.data();
|
|
13511
13915
|
const provider = requireEnabledProvider(params.provider);
|
|
13512
13916
|
if (!body.state) {
|
|
13513
|
-
throw new
|
|
13917
|
+
throw new ValidationError10({
|
|
13514
13918
|
message: "OAuth state is required. Ensure the OAuth interceptor is configured."
|
|
13515
13919
|
});
|
|
13516
13920
|
}
|
|
@@ -13811,6 +14215,10 @@ var mainAuthRouter = defineRouter6({
|
|
|
13811
14215
|
sendVerificationCode,
|
|
13812
14216
|
verifyCode,
|
|
13813
14217
|
register,
|
|
14218
|
+
// Verified-email signup routes
|
|
14219
|
+
requestSignupLink,
|
|
14220
|
+
confirmSignupLink,
|
|
14221
|
+
completeSignup,
|
|
13814
14222
|
login,
|
|
13815
14223
|
logout,
|
|
13816
14224
|
rotateKey,
|
|
@@ -13870,11 +14278,11 @@ init_types();
|
|
|
13870
14278
|
init_schema3();
|
|
13871
14279
|
|
|
13872
14280
|
// src/server/lib/crypto.ts
|
|
13873
|
-
import
|
|
14281
|
+
import crypto7 from "crypto";
|
|
13874
14282
|
import jwt3 from "jsonwebtoken";
|
|
13875
14283
|
function generateKeyPairES256() {
|
|
13876
|
-
const keyId =
|
|
13877
|
-
const { privateKey, publicKey } =
|
|
14284
|
+
const keyId = crypto7.randomUUID();
|
|
14285
|
+
const { privateKey, publicKey } = crypto7.generateKeyPairSync("ec", {
|
|
13878
14286
|
namedCurve: "P-256",
|
|
13879
14287
|
// ES256
|
|
13880
14288
|
publicKeyEncoding: {
|
|
@@ -13888,7 +14296,7 @@ function generateKeyPairES256() {
|
|
|
13888
14296
|
});
|
|
13889
14297
|
const privateKeyB64 = privateKey.toString("base64");
|
|
13890
14298
|
const publicKeyB64 = publicKey.toString("base64");
|
|
13891
|
-
const fingerprint =
|
|
14299
|
+
const fingerprint = crypto7.createHash("sha256").update(publicKey).digest("hex");
|
|
13892
14300
|
return {
|
|
13893
14301
|
privateKey: privateKeyB64,
|
|
13894
14302
|
publicKey: publicKeyB64,
|
|
@@ -13898,8 +14306,8 @@ function generateKeyPairES256() {
|
|
|
13898
14306
|
};
|
|
13899
14307
|
}
|
|
13900
14308
|
function generateKeyPairRS256() {
|
|
13901
|
-
const keyId =
|
|
13902
|
-
const { privateKey, publicKey } =
|
|
14309
|
+
const keyId = crypto7.randomUUID();
|
|
14310
|
+
const { privateKey, publicKey } = crypto7.generateKeyPairSync("rsa", {
|
|
13903
14311
|
modulusLength: 2048,
|
|
13904
14312
|
publicKeyEncoding: {
|
|
13905
14313
|
type: "spki",
|
|
@@ -13912,7 +14320,7 @@ function generateKeyPairRS256() {
|
|
|
13912
14320
|
});
|
|
13913
14321
|
const privateKeyB64 = privateKey.toString("base64");
|
|
13914
14322
|
const publicKeyB64 = publicKey.toString("base64");
|
|
13915
|
-
const fingerprint =
|
|
14323
|
+
const fingerprint = crypto7.createHash("sha256").update(publicKey).digest("hex");
|
|
13916
14324
|
return {
|
|
13917
14325
|
privateKey: privateKeyB64,
|
|
13918
14326
|
publicKey: publicKeyB64,
|
|
@@ -13927,7 +14335,7 @@ function generateKeyPair(algorithm = "ES256") {
|
|
|
13927
14335
|
function generateClientToken(payload, privateKeyB64, algorithm, options) {
|
|
13928
14336
|
try {
|
|
13929
14337
|
const privateKeyDER = Buffer.from(privateKeyB64, "base64");
|
|
13930
|
-
const privateKeyObject =
|
|
14338
|
+
const privateKeyObject = crypto7.createPrivateKey({
|
|
13931
14339
|
key: privateKeyDER,
|
|
13932
14340
|
format: "der",
|
|
13933
14341
|
type: "pkcs8"
|
|
@@ -13971,10 +14379,10 @@ function shouldRotateKey(createdAt, rotationDays = 90) {
|
|
|
13971
14379
|
|
|
13972
14380
|
// src/server/lib/session.ts
|
|
13973
14381
|
import * as jose2 from "jose";
|
|
13974
|
-
import { env as
|
|
14382
|
+
import { env as env13 } from "@spfn/auth/config";
|
|
13975
14383
|
import { env as coreEnv } from "@spfn/core/config";
|
|
13976
14384
|
async function getSessionSecretKey() {
|
|
13977
|
-
const secret =
|
|
14385
|
+
const secret = env13.SPFN_AUTH_SESSION_SECRET;
|
|
13978
14386
|
const encoder = new TextEncoder();
|
|
13979
14387
|
const data = encoder.encode(secret);
|
|
13980
14388
|
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
@@ -14056,14 +14464,14 @@ async function shouldRefreshSession(jwt4, thresholdHours = 24) {
|
|
|
14056
14464
|
}
|
|
14057
14465
|
|
|
14058
14466
|
// src/server/setup.ts
|
|
14059
|
-
import { env as
|
|
14467
|
+
import { env as env14 } from "@spfn/auth/config";
|
|
14060
14468
|
import { getRoleByName as getRoleByName2 } from "@spfn/auth/server";
|
|
14061
14469
|
init_repositories();
|
|
14062
14470
|
function parseAdminAccounts() {
|
|
14063
14471
|
const accounts = [];
|
|
14064
|
-
if (
|
|
14472
|
+
if (env14.SPFN_AUTH_ADMIN_ACCOUNTS) {
|
|
14065
14473
|
try {
|
|
14066
|
-
const accountsJson =
|
|
14474
|
+
const accountsJson = env14.SPFN_AUTH_ADMIN_ACCOUNTS;
|
|
14067
14475
|
const parsed = JSON.parse(accountsJson);
|
|
14068
14476
|
if (!Array.isArray(parsed)) {
|
|
14069
14477
|
authLogger.setup.error("\u274C SPFN_AUTH_ADMIN_ACCOUNTS must be an array");
|
|
@@ -14090,11 +14498,11 @@ function parseAdminAccounts() {
|
|
|
14090
14498
|
return accounts;
|
|
14091
14499
|
}
|
|
14092
14500
|
}
|
|
14093
|
-
const adminEmails =
|
|
14501
|
+
const adminEmails = env14.SPFN_AUTH_ADMIN_EMAILS;
|
|
14094
14502
|
if (adminEmails) {
|
|
14095
14503
|
const emails = adminEmails.split(",").map((s) => s.trim());
|
|
14096
|
-
const passwords = (
|
|
14097
|
-
const roles2 = (
|
|
14504
|
+
const passwords = (env14.SPFN_AUTH_ADMIN_PASSWORDS || "").split(",").map((s) => s.trim());
|
|
14505
|
+
const roles2 = (env14.SPFN_AUTH_ADMIN_ROLES || "").split(",").map((s) => s.trim());
|
|
14098
14506
|
if (passwords.length !== emails.length) {
|
|
14099
14507
|
authLogger.setup.error("\u274C SPFN_AUTH_ADMIN_EMAILS and SPFN_AUTH_ADMIN_PASSWORDS length mismatch");
|
|
14100
14508
|
return accounts;
|
|
@@ -14116,8 +14524,8 @@ function parseAdminAccounts() {
|
|
|
14116
14524
|
}
|
|
14117
14525
|
return accounts;
|
|
14118
14526
|
}
|
|
14119
|
-
const adminEmail =
|
|
14120
|
-
const adminPassword =
|
|
14527
|
+
const adminEmail = env14.SPFN_AUTH_ADMIN_EMAIL;
|
|
14528
|
+
const adminPassword = env14.SPFN_AUTH_ADMIN_PASSWORD;
|
|
14121
14529
|
if (adminEmail && adminPassword) {
|
|
14122
14530
|
accounts.push({
|
|
14123
14531
|
email: adminEmail,
|
|
@@ -14253,6 +14661,7 @@ export {
|
|
|
14253
14661
|
RolePermissionsRepository,
|
|
14254
14662
|
RolesRepository,
|
|
14255
14663
|
SOCIAL_PROVIDERS,
|
|
14664
|
+
SignupLinkTokensRepository,
|
|
14256
14665
|
SocialAccountsRepository,
|
|
14257
14666
|
TargetTypeSchema,
|
|
14258
14667
|
USER_STATUSES,
|
|
@@ -14287,9 +14696,11 @@ export {
|
|
|
14287
14696
|
cancelInvitation,
|
|
14288
14697
|
changePasswordService,
|
|
14289
14698
|
checkUsernameAvailableService,
|
|
14699
|
+
completeSignupService,
|
|
14290
14700
|
configureAuth,
|
|
14291
14701
|
configureDeletion,
|
|
14292
14702
|
configureOAuthTokenCipher,
|
|
14703
|
+
confirmSignupLinkService,
|
|
14293
14704
|
createAuthDeletionJobRouter,
|
|
14294
14705
|
createAuthDeletionPurgeJob,
|
|
14295
14706
|
createAuthLifecycle,
|
|
@@ -14360,6 +14771,7 @@ export {
|
|
|
14360
14771
|
isEncrypted,
|
|
14361
14772
|
isGoogleOAuthEnabled,
|
|
14362
14773
|
isOAuthProviderEnabled,
|
|
14774
|
+
isSafeReturnPath,
|
|
14363
14775
|
issueOneTimeTokenService,
|
|
14364
14776
|
issueOpsTokenService,
|
|
14365
14777
|
kakaoProvider,
|
|
@@ -14394,6 +14806,7 @@ export {
|
|
|
14394
14806
|
registerService,
|
|
14395
14807
|
removePermissionFromRole,
|
|
14396
14808
|
requestAccountDeletionService,
|
|
14809
|
+
requestSignupLinkService,
|
|
14397
14810
|
requireAnyPermission,
|
|
14398
14811
|
requireEnabledProvider,
|
|
14399
14812
|
requireOpsScope,
|
|
@@ -14418,6 +14831,8 @@ export {
|
|
|
14418
14831
|
setRolePermissions,
|
|
14419
14832
|
shouldRefreshSession,
|
|
14420
14833
|
shouldRotateKey,
|
|
14834
|
+
signupLinkTokens,
|
|
14835
|
+
signupLinkTokensRepository,
|
|
14421
14836
|
socialAccountsRepository,
|
|
14422
14837
|
sweepDuePurges,
|
|
14423
14838
|
unsealSession,
|