@spfn/auth 0.3.0-beta.7 → 0.3.0-beta.8
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 +378 -0
- package/dist/client-proof.d.ts +23 -12
- package/dist/client-proof.js +143 -4
- package/dist/client-proof.js.map +1 -1
- package/dist/config.d.ts +20 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +76 -3
- package/dist/errors.js +45 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +46 -6
- package/dist/index.js +75 -0
- package/dist/index.js.map +1 -1
- package/dist/{authenticate-Mg9D7Nys.d.ts → machine-principals-Bd5hp76H.d.ts} +343 -6
- package/dist/nextjs/api.js +190 -9
- package/dist/nextjs/api.js.map +1 -1
- package/dist/nextjs/server.js +59 -8
- package/dist/nextjs/server.js.map +1 -1
- package/dist/server.d.ts +719 -89
- package/dist/server.js +1383 -440
- package/dist/server.js.map +1 -1
- package/migrations/20260901091716_fine_arclight/migration.sql +21 -0
- package/migrations/20260901091716_fine_arclight/snapshot.json +3849 -0
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -4473,7 +4473,7 @@ var init_types = __esm({
|
|
|
4473
4473
|
|
|
4474
4474
|
// src/server/routes/schema.ts
|
|
4475
4475
|
import { EMAIL_PATTERN, PHONE_PATTERN } from "@spfn/auth";
|
|
4476
|
-
var EmailSchema, PhoneSchema, DeviceNameSchema, PlatformSchema, PasswordSchema, TargetTypeSchema, VERIFICATION_TARGET_TYPES, VerificationPurposeSchema, VERIFICATION_PURPOSES;
|
|
4476
|
+
var EmailSchema, PhoneSchema, DeviceNameSchema, PlatformSchema, PublicKeySchema, KeyIdSchema, FingerprintSchema, UserCodeSchema, DeviceAuthPollResponseSchema, PasswordSchema, TargetTypeSchema, VERIFICATION_TARGET_TYPES, VerificationPurposeSchema, VERIFICATION_PURPOSES;
|
|
4477
4477
|
var init_schema3 = __esm({
|
|
4478
4478
|
"src/server/routes/schema.ts"() {
|
|
4479
4479
|
"use strict";
|
|
@@ -4495,6 +4495,39 @@ var init_schema3 = __esm({
|
|
|
4495
4495
|
KEY_PLATFORM.map((platform) => Type.Literal(platform)),
|
|
4496
4496
|
{ description: "Platform the key lives on" }
|
|
4497
4497
|
);
|
|
4498
|
+
PublicKeySchema = Type.String({
|
|
4499
|
+
maxLength: 2048,
|
|
4500
|
+
description: "Client public key, SPKI DER in base64"
|
|
4501
|
+
});
|
|
4502
|
+
KeyIdSchema = Type.String({
|
|
4503
|
+
maxLength: 64,
|
|
4504
|
+
description: "Key identifier"
|
|
4505
|
+
});
|
|
4506
|
+
FingerprintSchema = Type.String({
|
|
4507
|
+
maxLength: 128,
|
|
4508
|
+
description: "SHA-256 hex fingerprint of the public key"
|
|
4509
|
+
});
|
|
4510
|
+
UserCodeSchema = Type.String({
|
|
4511
|
+
minLength: 8,
|
|
4512
|
+
maxLength: 16,
|
|
4513
|
+
description: "Device user code as displayed, e.g. WXYZ-2345. Dashes, spaces and case are ignored."
|
|
4514
|
+
});
|
|
4515
|
+
DeviceAuthPollResponseSchema = Type.Union([
|
|
4516
|
+
Type.Object({
|
|
4517
|
+
status: Type.Literal("pending"),
|
|
4518
|
+
intervalMillis: Type.Integer({ description: "Milliseconds to wait before polling again" })
|
|
4519
|
+
}),
|
|
4520
|
+
Type.Object({
|
|
4521
|
+
status: Type.Literal("approved"),
|
|
4522
|
+
userId: Type.String(),
|
|
4523
|
+
publicId: Type.String(),
|
|
4524
|
+
email: Type.Optional(Type.String()),
|
|
4525
|
+
phone: Type.Optional(Type.String()),
|
|
4526
|
+
passwordChangeRequired: Type.Boolean()
|
|
4527
|
+
})
|
|
4528
|
+
], {
|
|
4529
|
+
description: "Pending, or the login the approval produced"
|
|
4530
|
+
});
|
|
4498
4531
|
PasswordSchema = Type.String({
|
|
4499
4532
|
minLength: 8,
|
|
4500
4533
|
maxLength: 72,
|
|
@@ -5028,9 +5061,72 @@ var init_signup_link_tokens = __esm({
|
|
|
5028
5061
|
}
|
|
5029
5062
|
});
|
|
5030
5063
|
|
|
5064
|
+
// src/server/entities/device-authorizations.ts
|
|
5065
|
+
import { text as text8, uniqueIndex as uniqueIndex3 } from "drizzle-orm/pg-core";
|
|
5066
|
+
import { id as id8, timestamps as timestamps7, enumText as enumText5, utcTimestamp as utcTimestamp6, optionalForeignKey } from "@spfn/core/db";
|
|
5067
|
+
var DEVICE_AUTH_STATUSES, deviceAuthorizations;
|
|
5068
|
+
var init_device_authorizations = __esm({
|
|
5069
|
+
"src/server/entities/device-authorizations.ts"() {
|
|
5070
|
+
"use strict";
|
|
5071
|
+
init_types();
|
|
5072
|
+
init_users();
|
|
5073
|
+
init_schema4();
|
|
5074
|
+
DEVICE_AUTH_STATUSES = ["pending", "approved", "denied", "consumed"];
|
|
5075
|
+
deviceAuthorizations = authSchema.table(
|
|
5076
|
+
"device_authorizations",
|
|
5077
|
+
{
|
|
5078
|
+
id: id8(),
|
|
5079
|
+
// SHA-256 of the device code, hex
|
|
5080
|
+
// The code itself (32 random bytes, base64url) is returned to the waiting
|
|
5081
|
+
// device once and never stored: it is the only thing that device holds,
|
|
5082
|
+
// so a dump of this table must not let its reader finish someone's login
|
|
5083
|
+
deviceCodeHash: text8("device_code_hash").notNull().unique(),
|
|
5084
|
+
// The code a person reads off the waiting device's screen
|
|
5085
|
+
// Stored normalized — uppercase, no dash — because that is the only form
|
|
5086
|
+
// a lookup can match; the dash is put back for display
|
|
5087
|
+
// Plaintext on purpose: it authorizes nothing without an approver who is
|
|
5088
|
+
// already authenticated, and it has to be found by exact match
|
|
5089
|
+
userCode: text8("user_code").notNull(),
|
|
5090
|
+
// Key material the waiting device generated, same shapes as user_public_keys
|
|
5091
|
+
// Parked, not registered: it can sign nothing until a poll moves it over
|
|
5092
|
+
publicKey: text8("public_key").notNull(),
|
|
5093
|
+
keyId: text8("key_id").notNull(),
|
|
5094
|
+
fingerprint: text8("fingerprint").notNull(),
|
|
5095
|
+
algorithm: enumText5("algorithm", KEY_ALGORITHM).notNull().default("ES256"),
|
|
5096
|
+
// Device labels the waiting device supplied
|
|
5097
|
+
// null: it sent none
|
|
5098
|
+
// Shown to the approver so they can tell whether the device asking is the
|
|
5099
|
+
// one in front of them — display only, so a client that lies gains nothing
|
|
5100
|
+
// but a wrong line on the approval screen
|
|
5101
|
+
deviceName: text8("device_name"),
|
|
5102
|
+
platform: enumText5("platform", KEY_PLATFORM),
|
|
5103
|
+
status: enumText5("status", DEVICE_AUTH_STATUSES).notNull().default("pending"),
|
|
5104
|
+
// Who approved, written at approve time from the approver's session
|
|
5105
|
+
// null while pending, and on every record that was denied or expired
|
|
5106
|
+
// Never taken from a request body — that would be the whole authorization
|
|
5107
|
+
userId: optionalForeignKey("user", () => users.id, { onDelete: "cascade" }),
|
|
5108
|
+
// Expiry — 10 minutes from creation by default
|
|
5109
|
+
// Judged on read; no job clears the row
|
|
5110
|
+
expiresAt: utcTimestamp6("expires_at").notNull(),
|
|
5111
|
+
// Set when the approver said yes
|
|
5112
|
+
approvedAt: utcTimestamp6("approved_at"),
|
|
5113
|
+
// Set when the poll that registered the key won the race
|
|
5114
|
+
// Non-null means the record is spent, whatever else it says
|
|
5115
|
+
consumedAt: utcTimestamp6("consumed_at"),
|
|
5116
|
+
...timestamps7()
|
|
5117
|
+
},
|
|
5118
|
+
(table) => [
|
|
5119
|
+
// Lookup path for info/approve/deny
|
|
5120
|
+
// Unique so a typed code can never address two records
|
|
5121
|
+
uniqueIndex3("device_authorization_user_code_idx").on(table.userCode)
|
|
5122
|
+
]
|
|
5123
|
+
);
|
|
5124
|
+
}
|
|
5125
|
+
});
|
|
5126
|
+
|
|
5031
5127
|
// src/server/entities/user-invitations.ts
|
|
5032
|
-
import { text as
|
|
5033
|
-
import { id as
|
|
5128
|
+
import { text as text9, index as index8 } from "drizzle-orm/pg-core";
|
|
5129
|
+
import { id as id9, timestamps as timestamps8, enumText as enumText6, utcTimestamp as utcTimestamp7, typedJsonb as typedJsonb2, foreignKey as foreignKey5 } from "@spfn/core/db";
|
|
5034
5130
|
var userInvitations;
|
|
5035
5131
|
var init_user_invitations = __esm({
|
|
5036
5132
|
"src/server/entities/user-invitations.ts"() {
|
|
@@ -5043,14 +5139,14 @@ var init_user_invitations = __esm({
|
|
|
5043
5139
|
"user_invitations",
|
|
5044
5140
|
{
|
|
5045
5141
|
// Primary key
|
|
5046
|
-
id:
|
|
5142
|
+
id: id9(),
|
|
5047
5143
|
// Target email address for the invitation
|
|
5048
5144
|
// Will become the user's email upon acceptance
|
|
5049
|
-
email:
|
|
5145
|
+
email: text9("email").notNull(),
|
|
5050
5146
|
// Unique invitation token (UUID v4)
|
|
5051
5147
|
// Used in invitation URL: /auth/invite/{token}
|
|
5052
5148
|
// Single-use token that expires after acceptance
|
|
5053
|
-
token:
|
|
5149
|
+
token: text9("token").notNull().unique(),
|
|
5054
5150
|
// Role to be assigned when invitation is accepted
|
|
5055
5151
|
// Foreign key to roles table
|
|
5056
5152
|
roleId: foreignKey5("role", () => roles.id),
|
|
@@ -5063,19 +5159,19 @@ var init_user_invitations = __esm({
|
|
|
5063
5159
|
// - accepted: User accepted and account created
|
|
5064
5160
|
// - expired: Invitation expired (automatic)
|
|
5065
5161
|
// - cancelled: Invitation cancelled by admin
|
|
5066
|
-
status:
|
|
5162
|
+
status: enumText6("status", INVITATION_STATUSES).default("pending").notNull(),
|
|
5067
5163
|
// Expiration timestamp (default: 7 days from creation)
|
|
5068
5164
|
// Invitation cannot be accepted after this time
|
|
5069
5165
|
// Background job should update status to 'expired'
|
|
5070
|
-
expiresAt:
|
|
5166
|
+
expiresAt: utcTimestamp7("expires_at").notNull(),
|
|
5071
5167
|
// Timestamp when invitation was accepted
|
|
5072
5168
|
// null = not yet accepted
|
|
5073
5169
|
// Used for: audit trail, analytics
|
|
5074
|
-
acceptedAt:
|
|
5170
|
+
acceptedAt: utcTimestamp7("accepted_at"),
|
|
5075
5171
|
// Timestamp when invitation was cancelled
|
|
5076
5172
|
// null = not cancelled
|
|
5077
5173
|
// Used for: audit trail
|
|
5078
|
-
cancelledAt:
|
|
5174
|
+
cancelledAt: utcTimestamp7("cancelled_at"),
|
|
5079
5175
|
// Additional metadata (JSONB)
|
|
5080
5176
|
// Use cases:
|
|
5081
5177
|
// - Custom welcome message
|
|
@@ -5084,7 +5180,7 @@ var init_user_invitations = __esm({
|
|
|
5084
5180
|
// - Custom fields for app-specific data
|
|
5085
5181
|
// Example: { message: "Welcome!", department: "Engineering" }
|
|
5086
5182
|
metadata: typedJsonb2("metadata"),
|
|
5087
|
-
...
|
|
5183
|
+
...timestamps8()
|
|
5088
5184
|
},
|
|
5089
5185
|
(table) => [
|
|
5090
5186
|
// Indexes for query optimization
|
|
@@ -5101,9 +5197,9 @@ var init_user_invitations = __esm({
|
|
|
5101
5197
|
});
|
|
5102
5198
|
|
|
5103
5199
|
// src/server/entities/account-deletion-requests.ts
|
|
5104
|
-
import { text as
|
|
5200
|
+
import { text as text10, index as index9, uniqueIndex as uniqueIndex4 } from "drizzle-orm/pg-core";
|
|
5105
5201
|
import { sql as sql2 } from "drizzle-orm";
|
|
5106
|
-
import { id as
|
|
5202
|
+
import { id as id10, timestamps as timestamps9, enumText as enumText7, utcTimestamp as utcTimestamp8, optionalForeignKey as optionalForeignKey2 } from "@spfn/core/db";
|
|
5107
5203
|
var accountDeletionRequests;
|
|
5108
5204
|
var init_account_deletion_requests = __esm({
|
|
5109
5205
|
"src/server/entities/account-deletion-requests.ts"() {
|
|
@@ -5114,32 +5210,32 @@ var init_account_deletion_requests = __esm({
|
|
|
5114
5210
|
accountDeletionRequests = authSchema.table(
|
|
5115
5211
|
"account_deletion_requests",
|
|
5116
5212
|
{
|
|
5117
|
-
id:
|
|
5213
|
+
id: id10(),
|
|
5118
5214
|
// Foreign key to users table. `set null` (optionalForeignKey default) so this
|
|
5119
5215
|
// row survives a hard-delete purge of the user it refers to.
|
|
5120
|
-
userId:
|
|
5216
|
+
userId: optionalForeignKey2("user", () => users.id),
|
|
5121
5217
|
// Snapshot of the user's public UUID at request time — stays readable even
|
|
5122
5218
|
// after userId is nulled out or the account is anonymized.
|
|
5123
|
-
userPublicId:
|
|
5219
|
+
userPublicId: text10("user_public_id").notNull(),
|
|
5124
5220
|
// When the deletion was requested
|
|
5125
|
-
requestedAt:
|
|
5221
|
+
requestedAt: utcTimestamp8("requested_at").notNull().defaultNow(),
|
|
5126
5222
|
// When the purge job is allowed to run (requestedAt + grace period; equals
|
|
5127
5223
|
// requestedAt itself for immediate/zero-grace deletions)
|
|
5128
|
-
purgeScheduledAt:
|
|
5224
|
+
purgeScheduledAt: utcTimestamp8("purge_scheduled_at").notNull(),
|
|
5129
5225
|
// Request lifecycle status
|
|
5130
5226
|
// - pending: awaiting purgeScheduledAt (or immediate purge)
|
|
5131
5227
|
// - cancelled: recovered before purge
|
|
5132
5228
|
// - completed: purge ran
|
|
5133
|
-
status:
|
|
5229
|
+
status: enumText7("status", ACCOUNT_DELETION_REQUEST_STATUSES).default("pending").notNull(),
|
|
5134
5230
|
// Who initiated the request
|
|
5135
|
-
requestedBy:
|
|
5231
|
+
requestedBy: enumText7("requested_by", ACCOUNT_DELETION_REQUESTED_BY).default("self").notNull(),
|
|
5136
5232
|
// Optional free-text reason (self-service UI, admin note, DSR reference, ...)
|
|
5137
|
-
reason:
|
|
5138
|
-
cancelledAt:
|
|
5139
|
-
completedAt:
|
|
5233
|
+
reason: text10("reason"),
|
|
5234
|
+
cancelledAt: utcTimestamp8("cancelled_at"),
|
|
5235
|
+
completedAt: utcTimestamp8("completed_at"),
|
|
5140
5236
|
// Purge strategy actually executed (set on completion; null while pending)
|
|
5141
|
-
purgeStrategy:
|
|
5142
|
-
...
|
|
5237
|
+
purgeStrategy: enumText7("purge_strategy", PURGE_STRATEGIES),
|
|
5238
|
+
...timestamps9()
|
|
5143
5239
|
},
|
|
5144
5240
|
(table) => [
|
|
5145
5241
|
index9("account_deletion_requests_user_id_idx").on(table.userId),
|
|
@@ -5147,7 +5243,7 @@ var init_account_deletion_requests = __esm({
|
|
|
5147
5243
|
index9("account_deletion_requests_purge_scheduled_at_idx").on(table.purgeScheduledAt),
|
|
5148
5244
|
index9("account_deletion_requests_user_public_id_idx").on(table.userPublicId),
|
|
5149
5245
|
// Partial unique index: at most one pending request per user at a time.
|
|
5150
|
-
|
|
5246
|
+
uniqueIndex4("account_deletion_requests_user_pending_unique_idx").on(table.userId).where(sql2`${table.status} = 'pending'`)
|
|
5151
5247
|
]
|
|
5152
5248
|
);
|
|
5153
5249
|
}
|
|
@@ -5299,8 +5395,8 @@ var init_rbac = __esm({
|
|
|
5299
5395
|
});
|
|
5300
5396
|
|
|
5301
5397
|
// src/server/entities/permissions.ts
|
|
5302
|
-
import { text as
|
|
5303
|
-
import { id as
|
|
5398
|
+
import { text as text11, boolean as boolean4, index as index10 } from "drizzle-orm/pg-core";
|
|
5399
|
+
import { id as id11, timestamps as timestamps10, enumText as enumText8, typedJsonb as typedJsonb3 } from "@spfn/core/db";
|
|
5304
5400
|
var permissions;
|
|
5305
5401
|
var init_permissions = __esm({
|
|
5306
5402
|
"src/server/entities/permissions.ts"() {
|
|
@@ -5311,7 +5407,7 @@ var init_permissions = __esm({
|
|
|
5311
5407
|
"permissions",
|
|
5312
5408
|
{
|
|
5313
5409
|
// Primary key
|
|
5314
|
-
id:
|
|
5410
|
+
id: id11(),
|
|
5315
5411
|
// Permission identifier
|
|
5316
5412
|
// Format: resource:action or namespace:resource:action
|
|
5317
5413
|
// Examples:
|
|
@@ -5319,20 +5415,20 @@ var init_permissions = __esm({
|
|
|
5319
5415
|
// - Namespaced: 'auth:user:delete', 'cms:post:publish'
|
|
5320
5416
|
// Must be unique across all permissions
|
|
5321
5417
|
// Used in: permission checks, role assignments, API guards
|
|
5322
|
-
name:
|
|
5418
|
+
name: text11("name").notNull().unique(),
|
|
5323
5419
|
// Display name for UI
|
|
5324
5420
|
// Human-readable name shown in admin panels
|
|
5325
5421
|
// Example: "Delete Users", "Publish Posts"
|
|
5326
|
-
displayName:
|
|
5422
|
+
displayName: text11("display_name").notNull(),
|
|
5327
5423
|
// Permission description
|
|
5328
5424
|
// Detailed explanation of what this permission allows
|
|
5329
5425
|
// Example: "Allows deletion of user accounts from the system"
|
|
5330
|
-
description:
|
|
5426
|
+
description: text11("description"),
|
|
5331
5427
|
// Category for grouping
|
|
5332
5428
|
// Used for: organizing permissions in UI, filtering
|
|
5333
5429
|
// Built-in categories: auth, user, rbac, system
|
|
5334
5430
|
// Custom categories: any app-specific category
|
|
5335
|
-
category:
|
|
5431
|
+
category: enumText8("category", PERMISSION_CATEGORIES),
|
|
5336
5432
|
// Built-in permission flag
|
|
5337
5433
|
// true: Core package permissions (auth:*, user:*, rbac:*)
|
|
5338
5434
|
// - Cannot be deleted or modified
|
|
@@ -5363,7 +5459,7 @@ var init_permissions = __esm({
|
|
|
5363
5459
|
// - Audit: { createdBy: 123, source: 'migration', version: '1.0.0' }
|
|
5364
5460
|
// Example: { icon: 'trash', color: 'red', requiresMfa: true }
|
|
5365
5461
|
metadata: typedJsonb3("metadata"),
|
|
5366
|
-
...
|
|
5462
|
+
...timestamps10()
|
|
5367
5463
|
},
|
|
5368
5464
|
(table) => [
|
|
5369
5465
|
index10("permissions_name_idx").on(table.name),
|
|
@@ -5378,7 +5474,7 @@ var init_permissions = __esm({
|
|
|
5378
5474
|
|
|
5379
5475
|
// src/server/entities/role-permissions.ts
|
|
5380
5476
|
import { index as index11, unique } from "drizzle-orm/pg-core";
|
|
5381
|
-
import { id as
|
|
5477
|
+
import { id as id12, timestamps as timestamps11, foreignKey as foreignKey6 } from "@spfn/core/db";
|
|
5382
5478
|
var rolePermissions;
|
|
5383
5479
|
var init_role_permissions = __esm({
|
|
5384
5480
|
"src/server/entities/role-permissions.ts"() {
|
|
@@ -5390,7 +5486,7 @@ var init_role_permissions = __esm({
|
|
|
5390
5486
|
"role_permissions",
|
|
5391
5487
|
{
|
|
5392
5488
|
// Primary key
|
|
5393
|
-
id:
|
|
5489
|
+
id: id12(),
|
|
5394
5490
|
// Role reference
|
|
5395
5491
|
// Foreign key to roles table
|
|
5396
5492
|
// Cascade delete: when role is deleted, all role-permission mappings are removed
|
|
@@ -5403,7 +5499,7 @@ var init_role_permissions = __esm({
|
|
|
5403
5499
|
// Used for: granting permissions to roles
|
|
5404
5500
|
// Example: user:delete permission → [Admin, Superadmin]
|
|
5405
5501
|
permissionId: foreignKey6("permission", () => permissions.id, { onDelete: "cascade" }),
|
|
5406
|
-
...
|
|
5502
|
+
...timestamps11()
|
|
5407
5503
|
},
|
|
5408
5504
|
(table) => [
|
|
5409
5505
|
// Indexes for query performance
|
|
@@ -5417,8 +5513,8 @@ var init_role_permissions = __esm({
|
|
|
5417
5513
|
});
|
|
5418
5514
|
|
|
5419
5515
|
// src/server/entities/user-permissions.ts
|
|
5420
|
-
import { boolean as boolean5, text as
|
|
5421
|
-
import { id as
|
|
5516
|
+
import { boolean as boolean5, text as text12, index as index12, unique as unique2 } from "drizzle-orm/pg-core";
|
|
5517
|
+
import { id as id13, timestamps as timestamps12, utcTimestamp as utcTimestamp9, foreignKey as foreignKey7 } from "@spfn/core/db";
|
|
5422
5518
|
var userPermissions;
|
|
5423
5519
|
var init_user_permissions = __esm({
|
|
5424
5520
|
"src/server/entities/user-permissions.ts"() {
|
|
@@ -5430,7 +5526,7 @@ var init_user_permissions = __esm({
|
|
|
5430
5526
|
"user_permissions",
|
|
5431
5527
|
{
|
|
5432
5528
|
// Primary key
|
|
5433
|
-
id:
|
|
5529
|
+
id: id13(),
|
|
5434
5530
|
// User reference
|
|
5435
5531
|
// Foreign key to users table
|
|
5436
5532
|
// Cascade delete: when user is deleted, all overrides are removed
|
|
@@ -5453,13 +5549,13 @@ var init_user_permissions = __esm({
|
|
|
5453
5549
|
// Reason for grant/revocation
|
|
5454
5550
|
// Used for: audit trail, compliance documentation
|
|
5455
5551
|
// Example: "Temporary access for project X", "Security incident - restricted"
|
|
5456
|
-
reason:
|
|
5552
|
+
reason: text12("reason"),
|
|
5457
5553
|
// Expiration timestamp (optional)
|
|
5458
5554
|
// null: Permanent override (remains until manually removed)
|
|
5459
5555
|
// timestamp: Permission expires at this time (auto-revoked by background job)
|
|
5460
5556
|
// Use case: Time-limited elevated access, temporary restrictions
|
|
5461
|
-
expiresAt:
|
|
5462
|
-
...
|
|
5557
|
+
expiresAt: utcTimestamp9("expires_at"),
|
|
5558
|
+
...timestamps12()
|
|
5463
5559
|
},
|
|
5464
5560
|
(table) => [
|
|
5465
5561
|
// Indexes for query performance
|
|
@@ -5475,7 +5571,7 @@ var init_user_permissions = __esm({
|
|
|
5475
5571
|
|
|
5476
5572
|
// src/server/entities/auth-metadata.ts
|
|
5477
5573
|
import { sql as sql3 } from "drizzle-orm";
|
|
5478
|
-
import { text as
|
|
5574
|
+
import { text as text13, timestamp } from "drizzle-orm/pg-core";
|
|
5479
5575
|
var authMetadata;
|
|
5480
5576
|
var init_auth_metadata = __esm({
|
|
5481
5577
|
"src/server/entities/auth-metadata.ts"() {
|
|
@@ -5485,9 +5581,9 @@ var init_auth_metadata = __esm({
|
|
|
5485
5581
|
"auth_metadata",
|
|
5486
5582
|
{
|
|
5487
5583
|
// Metadata key (primary key)
|
|
5488
|
-
key:
|
|
5584
|
+
key: text13("key").primaryKey(),
|
|
5489
5585
|
// Metadata value
|
|
5490
|
-
value:
|
|
5586
|
+
value: text13("value").notNull(),
|
|
5491
5587
|
// Last updated timestamp — stamped by the database on insert and on update
|
|
5492
5588
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => sql3`now()`)
|
|
5493
5589
|
}
|
|
@@ -5496,8 +5592,8 @@ var init_auth_metadata = __esm({
|
|
|
5496
5592
|
});
|
|
5497
5593
|
|
|
5498
5594
|
// src/server/entities/ops-tokens.ts
|
|
5499
|
-
import { text as
|
|
5500
|
-
import { id as
|
|
5595
|
+
import { text as text14 } from "drizzle-orm/pg-core";
|
|
5596
|
+
import { id as id14, timestamps as timestamps13, utcTimestamp as utcTimestamp10 } from "@spfn/core/db";
|
|
5501
5597
|
var opsTokens;
|
|
5502
5598
|
var init_ops_tokens = __esm({
|
|
5503
5599
|
"src/server/entities/ops-tokens.ts"() {
|
|
@@ -5506,22 +5602,22 @@ var init_ops_tokens = __esm({
|
|
|
5506
5602
|
opsTokens = authSchema.table(
|
|
5507
5603
|
"ops_tokens",
|
|
5508
5604
|
{
|
|
5509
|
-
id:
|
|
5605
|
+
id: id14(),
|
|
5510
5606
|
// Operator-facing label ("ci-deploy", "rayim-laptop")
|
|
5511
|
-
name:
|
|
5607
|
+
name: text14("name").notNull(),
|
|
5512
5608
|
// SHA-256 hex of the token secret. Lookup key — the secret never lands
|
|
5513
5609
|
// here, and the unique constraint doubles as the lookup index.
|
|
5514
|
-
tokenHash:
|
|
5610
|
+
tokenHash: text14("token_hash").notNull().unique(),
|
|
5515
5611
|
// Granted scopes as permission strings ('waitlist:read', ...).
|
|
5516
5612
|
// '*' grants every scope.
|
|
5517
|
-
scopes:
|
|
5613
|
+
scopes: text14("scopes").array().notNull(),
|
|
5518
5614
|
// null = the token does not expire
|
|
5519
|
-
expiresAt:
|
|
5615
|
+
expiresAt: utcTimestamp10("expires_at"),
|
|
5520
5616
|
// null = active; a timestamp revokes the token permanently
|
|
5521
|
-
revokedAt:
|
|
5617
|
+
revokedAt: utcTimestamp10("revoked_at"),
|
|
5522
5618
|
// Last successful verification, updated fire-and-forget
|
|
5523
|
-
lastUsedAt:
|
|
5524
|
-
...
|
|
5619
|
+
lastUsedAt: utcTimestamp10("last_used_at"),
|
|
5620
|
+
...timestamps13()
|
|
5525
5621
|
}
|
|
5526
5622
|
);
|
|
5527
5623
|
}
|
|
@@ -5538,6 +5634,7 @@ var init_entities = __esm({
|
|
|
5538
5634
|
init_user_social_accounts();
|
|
5539
5635
|
init_verification_codes();
|
|
5540
5636
|
init_signup_link_tokens();
|
|
5637
|
+
init_device_authorizations();
|
|
5541
5638
|
init_user_invitations();
|
|
5542
5639
|
init_account_deletion_requests();
|
|
5543
5640
|
init_roles();
|
|
@@ -5564,8 +5661,8 @@ var init_users_repository = __esm({
|
|
|
5564
5661
|
* ID로 사용자 조회
|
|
5565
5662
|
* Read replica 사용
|
|
5566
5663
|
*/
|
|
5567
|
-
async findById(
|
|
5568
|
-
const result = await this.readDb.select().from(users).where(eq(users.id,
|
|
5664
|
+
async findById(id15) {
|
|
5665
|
+
const result = await this.readDb.select().from(users).where(eq(users.id, id15)).limit(1);
|
|
5569
5666
|
return result[0] ?? null;
|
|
5570
5667
|
}
|
|
5571
5668
|
/**
|
|
@@ -5575,8 +5672,8 @@ var init_users_repository = __esm({
|
|
|
5575
5672
|
* 안 되는 게이트(OAuth 세션 발급 등)가 사용한다. 일반 조회는 `findById`(replica)를
|
|
5576
5673
|
* 계속 사용할 것.
|
|
5577
5674
|
*/
|
|
5578
|
-
async findByIdOnPrimary(
|
|
5579
|
-
const result = await this.db.select().from(users).where(eq(users.id,
|
|
5675
|
+
async findByIdOnPrimary(id15) {
|
|
5676
|
+
const result = await this.db.select().from(users).where(eq(users.id, id15)).limit(1);
|
|
5580
5677
|
return result[0] ?? null;
|
|
5581
5678
|
}
|
|
5582
5679
|
/**
|
|
@@ -5647,13 +5744,13 @@ var init_users_repository = __esm({
|
|
|
5647
5744
|
*
|
|
5648
5745
|
* roleId가 null인 유저는 role: null 반환
|
|
5649
5746
|
*/
|
|
5650
|
-
async findByIdWithRole(
|
|
5747
|
+
async findByIdWithRole(id15) {
|
|
5651
5748
|
const result = await this.readDb.select({
|
|
5652
5749
|
user: users,
|
|
5653
5750
|
roleName: roles.name,
|
|
5654
5751
|
roleDisplayName: roles.displayName,
|
|
5655
5752
|
rolePriority: roles.priority
|
|
5656
|
-
}).from(users).leftJoin(roles, eq(users.roleId, roles.id)).where(eq(users.id,
|
|
5753
|
+
}).from(users).leftJoin(roles, eq(users.roleId, roles.id)).where(eq(users.id, id15)).limit(1);
|
|
5657
5754
|
const row = result[0];
|
|
5658
5755
|
if (!row) {
|
|
5659
5756
|
return null;
|
|
@@ -5728,9 +5825,9 @@ var init_users_repository = __esm({
|
|
|
5728
5825
|
* 사용자 정보 업데이트
|
|
5729
5826
|
* Write primary 사용
|
|
5730
5827
|
*/
|
|
5731
|
-
async updateById(
|
|
5828
|
+
async updateById(id15, data) {
|
|
5732
5829
|
const patch = "email" in data ? { ...data, email: normalizeOptionalEmail(data.email) } : data;
|
|
5733
|
-
const result = await this.db.update(users).set(patch).where(eq(users.id,
|
|
5830
|
+
const result = await this.db.update(users).set(patch).where(eq(users.id, id15)).returning();
|
|
5734
5831
|
return result[0] ?? null;
|
|
5735
5832
|
}
|
|
5736
5833
|
/**
|
|
@@ -5742,10 +5839,10 @@ var init_users_repository = __esm({
|
|
|
5742
5839
|
* status가 바뀐 상태) 시 null을 반환하며 예외를 던지지 않는다.
|
|
5743
5840
|
* Write primary 사용
|
|
5744
5841
|
*/
|
|
5745
|
-
async reactivateFromPendingDeletion(
|
|
5842
|
+
async reactivateFromPendingDeletion(id15) {
|
|
5746
5843
|
const result = await this.db.update(users).set({ status: "active" }).where(
|
|
5747
5844
|
and(
|
|
5748
|
-
eq(users.id,
|
|
5845
|
+
eq(users.id, id15),
|
|
5749
5846
|
eq(users.status, "pending_deletion")
|
|
5750
5847
|
)
|
|
5751
5848
|
).returning();
|
|
@@ -5755,32 +5852,32 @@ var init_users_repository = __esm({
|
|
|
5755
5852
|
* 비밀번호 업데이트
|
|
5756
5853
|
* Write primary 사용
|
|
5757
5854
|
*/
|
|
5758
|
-
async updatePassword(
|
|
5855
|
+
async updatePassword(id15, passwordHash, clearPasswordChangeRequired = true) {
|
|
5759
5856
|
const updateData = {
|
|
5760
5857
|
passwordHash
|
|
5761
5858
|
};
|
|
5762
5859
|
if (clearPasswordChangeRequired) {
|
|
5763
5860
|
updateData.passwordChangeRequired = false;
|
|
5764
5861
|
}
|
|
5765
|
-
const result = await this.db.update(users).set(updateData).where(eq(users.id,
|
|
5862
|
+
const result = await this.db.update(users).set(updateData).where(eq(users.id, id15)).returning();
|
|
5766
5863
|
return result[0] ?? null;
|
|
5767
5864
|
}
|
|
5768
5865
|
/**
|
|
5769
5866
|
* 마지막 로그인 시간 업데이트
|
|
5770
5867
|
* Write primary 사용
|
|
5771
5868
|
*/
|
|
5772
|
-
async updateLastLogin(
|
|
5869
|
+
async updateLastLogin(id15) {
|
|
5773
5870
|
const result = await this.db.update(users).set({
|
|
5774
5871
|
lastLoginAt: /* @__PURE__ */ new Date()
|
|
5775
|
-
}).where(eq(users.id,
|
|
5872
|
+
}).where(eq(users.id, id15)).returning();
|
|
5776
5873
|
return result[0] ?? null;
|
|
5777
5874
|
}
|
|
5778
5875
|
/**
|
|
5779
5876
|
* 사용자 삭제
|
|
5780
5877
|
* Write primary 사용
|
|
5781
5878
|
*/
|
|
5782
|
-
async deleteById(
|
|
5783
|
-
const result = await this.db.delete(users).where(eq(users.id,
|
|
5879
|
+
async deleteById(id15) {
|
|
5880
|
+
const result = await this.db.delete(users).where(eq(users.id, id15)).returning();
|
|
5784
5881
|
return result[0] ?? null;
|
|
5785
5882
|
}
|
|
5786
5883
|
/**
|
|
@@ -6128,14 +6225,14 @@ var init_keys_repository = __esm({
|
|
|
6128
6225
|
* stored, so it answers "since when has this device been on this release"
|
|
6129
6226
|
* rather than "when was it last seen", which lastUsedAt already answers.
|
|
6130
6227
|
*/
|
|
6131
|
-
async updateLastUsedById(
|
|
6228
|
+
async updateLastUsedById(id15, identity) {
|
|
6132
6229
|
const staleBefore = new Date(Date.now() - LAST_USED_THROTTLE_MS);
|
|
6133
6230
|
const lastUsedIsStale = or(
|
|
6134
6231
|
isNull(userPublicKeys.lastUsedAt),
|
|
6135
6232
|
lt(userPublicKeys.lastUsedAt, staleBefore)
|
|
6136
6233
|
);
|
|
6137
6234
|
if (!identity) {
|
|
6138
|
-
await this.db.update(userPublicKeys).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(and2(eq2(userPublicKeys.id,
|
|
6235
|
+
await this.db.update(userPublicKeys).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(and2(eq2(userPublicKeys.id, id15), lastUsedIsStale));
|
|
6139
6236
|
return;
|
|
6140
6237
|
}
|
|
6141
6238
|
const identityChanged = sql5`(
|
|
@@ -6152,7 +6249,7 @@ var init_keys_repository = __esm({
|
|
|
6152
6249
|
clientContractVersion: identity.contractVersion,
|
|
6153
6250
|
clientSeenAt: sql5`CASE WHEN ${identityChanged} THEN ${nowParam} ELSE ${userPublicKeys.clientSeenAt} END`
|
|
6154
6251
|
}).where(and2(
|
|
6155
|
-
eq2(userPublicKeys.id,
|
|
6252
|
+
eq2(userPublicKeys.id, id15),
|
|
6156
6253
|
or(lastUsedIsStale, identityChanged)
|
|
6157
6254
|
));
|
|
6158
6255
|
}
|
|
@@ -6191,8 +6288,8 @@ var init_verification_codes_repository = __esm({
|
|
|
6191
6288
|
* ID로 인증 코드 조회
|
|
6192
6289
|
* Read replica 사용
|
|
6193
6290
|
*/
|
|
6194
|
-
async findById(
|
|
6195
|
-
const result = await this.readDb.select().from(verificationCodes).where(eq3(verificationCodes.id,
|
|
6291
|
+
async findById(id15) {
|
|
6292
|
+
const result = await this.readDb.select().from(verificationCodes).where(eq3(verificationCodes.id, id15)).limit(1);
|
|
6196
6293
|
return result[0] ?? null;
|
|
6197
6294
|
}
|
|
6198
6295
|
/**
|
|
@@ -6206,22 +6303,22 @@ var init_verification_codes_repository = __esm({
|
|
|
6206
6303
|
* 인증 코드 사용 처리
|
|
6207
6304
|
* Write primary 사용
|
|
6208
6305
|
*/
|
|
6209
|
-
async markAsUsed(
|
|
6306
|
+
async markAsUsed(id15) {
|
|
6210
6307
|
const result = await this.db.update(verificationCodes).set({
|
|
6211
6308
|
usedAt: /* @__PURE__ */ new Date()
|
|
6212
|
-
}).where(eq3(verificationCodes.id,
|
|
6309
|
+
}).where(eq3(verificationCodes.id, id15)).returning();
|
|
6213
6310
|
return result[0] ?? null;
|
|
6214
6311
|
}
|
|
6215
6312
|
/**
|
|
6216
6313
|
* 시도 횟수 증가
|
|
6217
6314
|
* Write primary 사용
|
|
6218
6315
|
*/
|
|
6219
|
-
async incrementAttempts(
|
|
6220
|
-
const code = await this.findById(
|
|
6316
|
+
async incrementAttempts(id15) {
|
|
6317
|
+
const code = await this.findById(id15);
|
|
6221
6318
|
if (!code) return null;
|
|
6222
6319
|
const result = await this.db.update(verificationCodes).set({
|
|
6223
6320
|
attempts: code.attempts + 1
|
|
6224
|
-
}).where(eq3(verificationCodes.id,
|
|
6321
|
+
}).where(eq3(verificationCodes.id, id15)).returning();
|
|
6225
6322
|
return result[0] ?? null;
|
|
6226
6323
|
}
|
|
6227
6324
|
/**
|
|
@@ -6309,14 +6406,14 @@ var init_signup_link_tokens_repository = __esm({
|
|
|
6309
6406
|
*
|
|
6310
6407
|
* @returns the updated row, or null if another request claimed it first
|
|
6311
6408
|
*/
|
|
6312
|
-
async claimLink(
|
|
6409
|
+
async claimLink(id15, setupSecretHash, setupExpiresAt) {
|
|
6313
6410
|
const result = await this.db.update(signupLinkTokens).set({
|
|
6314
6411
|
consumedAt: /* @__PURE__ */ new Date(),
|
|
6315
6412
|
setupSecretHash,
|
|
6316
6413
|
setupExpiresAt
|
|
6317
6414
|
}).where(
|
|
6318
6415
|
and4(
|
|
6319
|
-
eq4(signupLinkTokens.id,
|
|
6416
|
+
eq4(signupLinkTokens.id, id15),
|
|
6320
6417
|
isNull3(signupLinkTokens.consumedAt),
|
|
6321
6418
|
isNull3(signupLinkTokens.supersededAt)
|
|
6322
6419
|
)
|
|
@@ -6329,10 +6426,10 @@ var init_signup_link_tokens_repository = __esm({
|
|
|
6329
6426
|
*
|
|
6330
6427
|
* @returns the updated row, or null if another request completed it first
|
|
6331
6428
|
*/
|
|
6332
|
-
async claimSetupSession(
|
|
6429
|
+
async claimSetupSession(id15) {
|
|
6333
6430
|
const result = await this.db.update(signupLinkTokens).set({ completedAt: /* @__PURE__ */ new Date() }).where(
|
|
6334
6431
|
and4(
|
|
6335
|
-
eq4(signupLinkTokens.id,
|
|
6432
|
+
eq4(signupLinkTokens.id, id15),
|
|
6336
6433
|
isNull3(signupLinkTokens.completedAt),
|
|
6337
6434
|
isNull3(signupLinkTokens.supersededAt)
|
|
6338
6435
|
)
|
|
@@ -6374,27 +6471,172 @@ var init_signup_link_tokens_repository = __esm({
|
|
|
6374
6471
|
}
|
|
6375
6472
|
});
|
|
6376
6473
|
|
|
6377
|
-
// src/server/repositories/
|
|
6474
|
+
// src/server/repositories/device-authorizations.repository.ts
|
|
6378
6475
|
import { BaseRepository as BaseRepository5 } from "@spfn/core/db";
|
|
6379
|
-
import { eq as eq5,
|
|
6476
|
+
import { eq as eq5, and as and5, gt as gt2, inArray, sql as sql6 } from "drizzle-orm";
|
|
6477
|
+
var notExpired, DeviceAuthorizationsRepository, deviceAuthorizationsRepository;
|
|
6478
|
+
var init_device_authorizations_repository = __esm({
|
|
6479
|
+
"src/server/repositories/device-authorizations.repository.ts"() {
|
|
6480
|
+
"use strict";
|
|
6481
|
+
init_device_authorizations();
|
|
6482
|
+
notExpired = () => gt2(deviceAuthorizations.expiresAt, sql6`now()`);
|
|
6483
|
+
DeviceAuthorizationsRepository = class extends BaseRepository5 {
|
|
6484
|
+
/**
|
|
6485
|
+
* Insert a pending authorization, unless one of its codes is already taken.
|
|
6486
|
+
*
|
|
6487
|
+
* `onConflictDoNothing` rather than letting the unique index raise: the start
|
|
6488
|
+
* route runs inside a transaction, and a raised unique violation aborts it,
|
|
6489
|
+
* so the retry that a generated-code collision calls for could not run — every
|
|
6490
|
+
* statement after it would fail with "current transaction is aborted" instead.
|
|
6491
|
+
* An empty result is the collision signal, and the caller draws a fresh code.
|
|
6492
|
+
*
|
|
6493
|
+
* Write primary.
|
|
6494
|
+
*
|
|
6495
|
+
* @returns the inserted row, or null if either code collided
|
|
6496
|
+
*/
|
|
6497
|
+
async create(data) {
|
|
6498
|
+
const result = await this.db.insert(deviceAuthorizations).values(data).onConflictDoNothing().returning();
|
|
6499
|
+
return result[0] ?? null;
|
|
6500
|
+
}
|
|
6501
|
+
/**
|
|
6502
|
+
* Find a record by its normalized user code, in any state.
|
|
6503
|
+
*
|
|
6504
|
+
* Deliberately unfiltered: which refusal a caller is owed — expired, already
|
|
6505
|
+
* handled, unknown — is the service's decision, and a row filtered out here
|
|
6506
|
+
* would be indistinguishable from a code that was never issued.
|
|
6507
|
+
*
|
|
6508
|
+
* Read replica.
|
|
6509
|
+
*/
|
|
6510
|
+
async findByUserCode(userCode) {
|
|
6511
|
+
const result = await this.readDb.select().from(deviceAuthorizations).where(eq5(deviceAuthorizations.userCode, userCode)).limit(1);
|
|
6512
|
+
return result[0] ?? null;
|
|
6513
|
+
}
|
|
6514
|
+
/**
|
|
6515
|
+
* Find a record by the hash of a device code, in any state.
|
|
6516
|
+
* Unfiltered for the same reason as `findByUserCode`.
|
|
6517
|
+
*
|
|
6518
|
+
* Read replica.
|
|
6519
|
+
*/
|
|
6520
|
+
async findByDeviceCodeHash(deviceCodeHash) {
|
|
6521
|
+
const result = await this.readDb.select().from(deviceAuthorizations).where(eq5(deviceAuthorizations.deviceCodeHash, deviceCodeHash)).limit(1);
|
|
6522
|
+
return result[0] ?? null;
|
|
6523
|
+
}
|
|
6524
|
+
/**
|
|
6525
|
+
* Bind the approving user and move the record to `approved`, but only from
|
|
6526
|
+
* `pending`.
|
|
6527
|
+
*
|
|
6528
|
+
* `userId` comes from the approver's authenticated session — never from a
|
|
6529
|
+
* request body — so this is the point where the record gains an owner.
|
|
6530
|
+
*
|
|
6531
|
+
* @returns the updated row, or null if it was no longer pending, or expired
|
|
6532
|
+
*/
|
|
6533
|
+
async approve(id15, userId) {
|
|
6534
|
+
const result = await this.db.update(deviceAuthorizations).set({ status: "approved", userId, approvedAt: /* @__PURE__ */ new Date() }).where(
|
|
6535
|
+
and5(
|
|
6536
|
+
eq5(deviceAuthorizations.id, id15),
|
|
6537
|
+
eq5(deviceAuthorizations.status, "pending"),
|
|
6538
|
+
notExpired()
|
|
6539
|
+
)
|
|
6540
|
+
).returning();
|
|
6541
|
+
return result[0] ?? null;
|
|
6542
|
+
}
|
|
6543
|
+
/**
|
|
6544
|
+
* Move the record to `denied`, but only from `pending`.
|
|
6545
|
+
*
|
|
6546
|
+
* No user is bound: a refusal should leave no record of who was asked.
|
|
6547
|
+
*
|
|
6548
|
+
* @returns the updated row, or null if it was no longer pending, or expired
|
|
6549
|
+
*/
|
|
6550
|
+
async deny(id15) {
|
|
6551
|
+
const result = await this.db.update(deviceAuthorizations).set({ status: "denied" }).where(
|
|
6552
|
+
and5(
|
|
6553
|
+
eq5(deviceAuthorizations.id, id15),
|
|
6554
|
+
eq5(deviceAuthorizations.status, "pending"),
|
|
6555
|
+
notExpired()
|
|
6556
|
+
)
|
|
6557
|
+
).returning();
|
|
6558
|
+
return result[0] ?? null;
|
|
6559
|
+
}
|
|
6560
|
+
/**
|
|
6561
|
+
* Refuse every authorization a user still has in flight.
|
|
6562
|
+
*
|
|
6563
|
+
* This is the device-code half of a global revocation. Revoking
|
|
6564
|
+
* `user_public_keys` alone leaves an approved-but-uncollected record behind,
|
|
6565
|
+
* and the next poll on it registers a brand-new active key — so "sign every
|
|
6566
|
+
* device out" would hand one straight back to whoever was still waiting,
|
|
6567
|
+
* which is exactly the person a revoke-all is usually aimed at.
|
|
6568
|
+
*
|
|
6569
|
+
* `denied` rather than a status of its own: a swept record owes its holder
|
|
6570
|
+
* the answer a refused one owes, on all four operations — the waiting device
|
|
6571
|
+
* is told no and stops polling, and info/approve/deny say the request was
|
|
6572
|
+
* already answered. Adding a fourth live status would add a migration and a
|
|
6573
|
+
* case-table row to record a distinction nothing acts on.
|
|
6574
|
+
*
|
|
6575
|
+
* Expired rows are swept too. A global revoke is not the place to reason
|
|
6576
|
+
* about which dead rows were about to die anyway.
|
|
6577
|
+
*
|
|
6578
|
+
* `pending` alongside `approved` even though a pending row carries no
|
|
6579
|
+
* `userId` today, so only approved rows can match: the state list says which
|
|
6580
|
+
* states this is meant to close, and a later change that binds the user
|
|
6581
|
+
* earlier should not silently reopen the hole.
|
|
6582
|
+
*
|
|
6583
|
+
* @returns the rows this call refused
|
|
6584
|
+
*/
|
|
6585
|
+
async denyAllActiveByUserId(userId) {
|
|
6586
|
+
return await this.db.update(deviceAuthorizations).set({ status: "denied" }).where(
|
|
6587
|
+
and5(
|
|
6588
|
+
eq5(deviceAuthorizations.userId, userId),
|
|
6589
|
+
inArray(deviceAuthorizations.status, ["pending", "approved"])
|
|
6590
|
+
)
|
|
6591
|
+
).returning();
|
|
6592
|
+
}
|
|
6593
|
+
/**
|
|
6594
|
+
* Spend an approved record, but only from `approved`, and address it by the
|
|
6595
|
+
* device code hash the caller actually presented.
|
|
6596
|
+
*
|
|
6597
|
+
* This is the one-shot: the winner of two concurrent polls registers the key,
|
|
6598
|
+
* and the loser sees no approved record and is answered as if the code were
|
|
6599
|
+
* unknown. Matching on the hash rather than on an id read a moment ago keeps
|
|
6600
|
+
* the whole decision in one statement.
|
|
6601
|
+
*
|
|
6602
|
+
* @returns the spent row, or null if it was not approved (any more), or expired
|
|
6603
|
+
*/
|
|
6604
|
+
async consumeApproved(deviceCodeHash) {
|
|
6605
|
+
const result = await this.db.update(deviceAuthorizations).set({ status: "consumed", consumedAt: /* @__PURE__ */ new Date() }).where(
|
|
6606
|
+
and5(
|
|
6607
|
+
eq5(deviceAuthorizations.deviceCodeHash, deviceCodeHash),
|
|
6608
|
+
eq5(deviceAuthorizations.status, "approved"),
|
|
6609
|
+
notExpired()
|
|
6610
|
+
)
|
|
6611
|
+
).returning();
|
|
6612
|
+
return result[0] ?? null;
|
|
6613
|
+
}
|
|
6614
|
+
};
|
|
6615
|
+
deviceAuthorizationsRepository = new DeviceAuthorizationsRepository();
|
|
6616
|
+
}
|
|
6617
|
+
});
|
|
6618
|
+
|
|
6619
|
+
// src/server/repositories/roles.repository.ts
|
|
6620
|
+
import { BaseRepository as BaseRepository6 } from "@spfn/core/db";
|
|
6621
|
+
import { eq as eq6, asc } from "drizzle-orm";
|
|
6380
6622
|
var RolesRepository, rolesRepository;
|
|
6381
6623
|
var init_roles_repository = __esm({
|
|
6382
6624
|
"src/server/repositories/roles.repository.ts"() {
|
|
6383
6625
|
"use strict";
|
|
6384
6626
|
init_roles();
|
|
6385
|
-
RolesRepository = class extends
|
|
6627
|
+
RolesRepository = class extends BaseRepository6 {
|
|
6386
6628
|
/**
|
|
6387
6629
|
* ID로 역할 조회
|
|
6388
6630
|
*/
|
|
6389
|
-
async findById(
|
|
6390
|
-
const result = await this.readDb.select().from(roles).where(
|
|
6631
|
+
async findById(id15) {
|
|
6632
|
+
const result = await this.readDb.select().from(roles).where(eq6(roles.id, id15)).limit(1);
|
|
6391
6633
|
return result[0] ?? null;
|
|
6392
6634
|
}
|
|
6393
6635
|
/**
|
|
6394
6636
|
* Name으로 역할 조회
|
|
6395
6637
|
*/
|
|
6396
6638
|
async findByName(name) {
|
|
6397
|
-
const result = await this.readDb.select().from(roles).where(
|
|
6639
|
+
const result = await this.readDb.select().from(roles).where(eq6(roles.name, name)).limit(1);
|
|
6398
6640
|
return result[0] ?? null;
|
|
6399
6641
|
}
|
|
6400
6642
|
/**
|
|
@@ -6407,7 +6649,7 @@ var init_roles_repository = __esm({
|
|
|
6407
6649
|
* 활성 역할만 조회
|
|
6408
6650
|
*/
|
|
6409
6651
|
async findActive() {
|
|
6410
|
-
return this.readDb.select().from(roles).where(
|
|
6652
|
+
return this.readDb.select().from(roles).where(eq6(roles.isActive, true)).orderBy(asc(roles.priority));
|
|
6411
6653
|
}
|
|
6412
6654
|
/**
|
|
6413
6655
|
* 역할 생성
|
|
@@ -6418,15 +6660,15 @@ var init_roles_repository = __esm({
|
|
|
6418
6660
|
/**
|
|
6419
6661
|
* 역할 업데이트
|
|
6420
6662
|
*/
|
|
6421
|
-
async updateById(
|
|
6422
|
-
const result = await this.db.update(roles).set(data).where(
|
|
6663
|
+
async updateById(id15, data) {
|
|
6664
|
+
const result = await this.db.update(roles).set(data).where(eq6(roles.id, id15)).returning();
|
|
6423
6665
|
return result[0] ?? null;
|
|
6424
6666
|
}
|
|
6425
6667
|
/**
|
|
6426
6668
|
* 역할 삭제
|
|
6427
6669
|
*/
|
|
6428
|
-
async deleteById(
|
|
6429
|
-
const result = await this.db.delete(roles).where(
|
|
6670
|
+
async deleteById(id15) {
|
|
6671
|
+
const result = await this.db.delete(roles).where(eq6(roles.id, id15)).returning();
|
|
6430
6672
|
return result[0] ?? null;
|
|
6431
6673
|
}
|
|
6432
6674
|
};
|
|
@@ -6435,26 +6677,26 @@ var init_roles_repository = __esm({
|
|
|
6435
6677
|
});
|
|
6436
6678
|
|
|
6437
6679
|
// src/server/repositories/permissions.repository.ts
|
|
6438
|
-
import { BaseRepository as
|
|
6439
|
-
import { asc as asc2, eq as
|
|
6680
|
+
import { BaseRepository as BaseRepository7 } from "@spfn/core/db";
|
|
6681
|
+
import { asc as asc2, eq as eq7, inArray as inArray2 } from "drizzle-orm";
|
|
6440
6682
|
var PermissionsRepository, permissionsRepository;
|
|
6441
6683
|
var init_permissions_repository = __esm({
|
|
6442
6684
|
"src/server/repositories/permissions.repository.ts"() {
|
|
6443
6685
|
"use strict";
|
|
6444
6686
|
init_permissions();
|
|
6445
|
-
PermissionsRepository = class extends
|
|
6687
|
+
PermissionsRepository = class extends BaseRepository7 {
|
|
6446
6688
|
/**
|
|
6447
6689
|
* ID로 권한 조회
|
|
6448
6690
|
*/
|
|
6449
|
-
async findById(
|
|
6450
|
-
const result = await this.readDb.select().from(permissions).where(
|
|
6691
|
+
async findById(id15) {
|
|
6692
|
+
const result = await this.readDb.select().from(permissions).where(eq7(permissions.id, id15)).limit(1);
|
|
6451
6693
|
return result[0] ?? null;
|
|
6452
6694
|
}
|
|
6453
6695
|
/**
|
|
6454
6696
|
* Name으로 권한 조회
|
|
6455
6697
|
*/
|
|
6456
6698
|
async findByName(name) {
|
|
6457
|
-
const result = await this.readDb.select().from(permissions).where(
|
|
6699
|
+
const result = await this.readDb.select().from(permissions).where(eq7(permissions.name, name)).limit(1);
|
|
6458
6700
|
return result[0] ?? null;
|
|
6459
6701
|
}
|
|
6460
6702
|
/**
|
|
@@ -6462,7 +6704,7 @@ var init_permissions_repository = __esm({
|
|
|
6462
6704
|
*/
|
|
6463
6705
|
async findByNames(names) {
|
|
6464
6706
|
if (names.length === 0) return [];
|
|
6465
|
-
return this.readDb.select().from(permissions).where(
|
|
6707
|
+
return this.readDb.select().from(permissions).where(inArray2(permissions.name, names));
|
|
6466
6708
|
}
|
|
6467
6709
|
/**
|
|
6468
6710
|
* 모든 권한 조회
|
|
@@ -6474,13 +6716,13 @@ var init_permissions_repository = __esm({
|
|
|
6474
6716
|
* 활성 권한만 조회
|
|
6475
6717
|
*/
|
|
6476
6718
|
async findActive() {
|
|
6477
|
-
return this.readDb.select().from(permissions).where(
|
|
6719
|
+
return this.readDb.select().from(permissions).where(eq7(permissions.isActive, true)).orderBy(asc2(permissions.name));
|
|
6478
6720
|
}
|
|
6479
6721
|
/**
|
|
6480
6722
|
* 카테고리별 권한 조회
|
|
6481
6723
|
*/
|
|
6482
6724
|
async findByCategory(category) {
|
|
6483
|
-
return this.readDb.select().from(permissions).where(
|
|
6725
|
+
return this.readDb.select().from(permissions).where(eq7(permissions.category, category)).orderBy(asc2(permissions.name));
|
|
6484
6726
|
}
|
|
6485
6727
|
/**
|
|
6486
6728
|
* 권한 생성
|
|
@@ -6498,15 +6740,15 @@ var init_permissions_repository = __esm({
|
|
|
6498
6740
|
/**
|
|
6499
6741
|
* 권한 업데이트
|
|
6500
6742
|
*/
|
|
6501
|
-
async updateById(
|
|
6502
|
-
const result = await this.db.update(permissions).set(data).where(
|
|
6743
|
+
async updateById(id15, data) {
|
|
6744
|
+
const result = await this.db.update(permissions).set(data).where(eq7(permissions.id, id15)).returning();
|
|
6503
6745
|
return result[0] ?? null;
|
|
6504
6746
|
}
|
|
6505
6747
|
/**
|
|
6506
6748
|
* 권한 삭제
|
|
6507
6749
|
*/
|
|
6508
|
-
async deleteById(
|
|
6509
|
-
const result = await this.db.delete(permissions).where(
|
|
6750
|
+
async deleteById(id15) {
|
|
6751
|
+
const result = await this.db.delete(permissions).where(eq7(permissions.id, id15)).returning();
|
|
6510
6752
|
return result[0] ?? null;
|
|
6511
6753
|
}
|
|
6512
6754
|
};
|
|
@@ -6515,25 +6757,25 @@ var init_permissions_repository = __esm({
|
|
|
6515
6757
|
});
|
|
6516
6758
|
|
|
6517
6759
|
// src/server/repositories/role-permissions.repository.ts
|
|
6518
|
-
import { BaseRepository as
|
|
6519
|
-
import { and as
|
|
6760
|
+
import { BaseRepository as BaseRepository8 } from "@spfn/core/db";
|
|
6761
|
+
import { and as and6, eq as eq8 } from "drizzle-orm";
|
|
6520
6762
|
var RolePermissionsRepository, rolePermissionsRepository;
|
|
6521
6763
|
var init_role_permissions_repository = __esm({
|
|
6522
6764
|
"src/server/repositories/role-permissions.repository.ts"() {
|
|
6523
6765
|
"use strict";
|
|
6524
6766
|
init_role_permissions();
|
|
6525
|
-
RolePermissionsRepository = class extends
|
|
6767
|
+
RolePermissionsRepository = class extends BaseRepository8 {
|
|
6526
6768
|
/**
|
|
6527
6769
|
* 역할 ID로 모든 권한 조회
|
|
6528
6770
|
*/
|
|
6529
6771
|
async findByRoleId(roleId) {
|
|
6530
|
-
return this.readDb.select().from(rolePermissions).where(
|
|
6772
|
+
return this.readDb.select().from(rolePermissions).where(eq8(rolePermissions.roleId, roleId));
|
|
6531
6773
|
}
|
|
6532
6774
|
/**
|
|
6533
6775
|
* 권한 ID로 모든 역할 조회
|
|
6534
6776
|
*/
|
|
6535
6777
|
async findByPermissionId(permissionId) {
|
|
6536
|
-
return this.readDb.select().from(rolePermissions).where(
|
|
6778
|
+
return this.readDb.select().from(rolePermissions).where(eq8(rolePermissions.permissionId, permissionId));
|
|
6537
6779
|
}
|
|
6538
6780
|
/**
|
|
6539
6781
|
* 역할-권한 매핑 생성
|
|
@@ -6553,9 +6795,9 @@ var init_role_permissions_repository = __esm({
|
|
|
6553
6795
|
*/
|
|
6554
6796
|
async deleteByRoleIdAndPermissionId(roleId, permissionId) {
|
|
6555
6797
|
const result = await this.db.delete(rolePermissions).where(
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6798
|
+
and6(
|
|
6799
|
+
eq8(rolePermissions.roleId, roleId),
|
|
6800
|
+
eq8(rolePermissions.permissionId, permissionId)
|
|
6559
6801
|
)
|
|
6560
6802
|
).returning();
|
|
6561
6803
|
return result[0] ?? null;
|
|
@@ -6564,7 +6806,7 @@ var init_role_permissions_repository = __esm({
|
|
|
6564
6806
|
* 역할의 모든 권한 매핑 삭제
|
|
6565
6807
|
*/
|
|
6566
6808
|
async deleteByRoleId(roleId) {
|
|
6567
|
-
const result = await this.db.delete(rolePermissions).where(
|
|
6809
|
+
const result = await this.db.delete(rolePermissions).where(eq8(rolePermissions.roleId, roleId)).returning();
|
|
6568
6810
|
return result.length;
|
|
6569
6811
|
}
|
|
6570
6812
|
/**
|
|
@@ -6585,19 +6827,19 @@ var init_role_permissions_repository = __esm({
|
|
|
6585
6827
|
});
|
|
6586
6828
|
|
|
6587
6829
|
// src/server/repositories/user-permissions.repository.ts
|
|
6588
|
-
import { BaseRepository as
|
|
6589
|
-
import { eq as
|
|
6830
|
+
import { BaseRepository as BaseRepository9 } from "@spfn/core/db";
|
|
6831
|
+
import { eq as eq9, and as and7, or as or2, isNull as isNull4, isNotNull, lt as lt3, gt as gt3 } from "drizzle-orm";
|
|
6590
6832
|
var UserPermissionsRepository, userPermissionsRepository;
|
|
6591
6833
|
var init_user_permissions_repository = __esm({
|
|
6592
6834
|
"src/server/repositories/user-permissions.repository.ts"() {
|
|
6593
6835
|
"use strict";
|
|
6594
6836
|
init_user_permissions();
|
|
6595
|
-
UserPermissionsRepository = class extends
|
|
6837
|
+
UserPermissionsRepository = class extends BaseRepository9 {
|
|
6596
6838
|
/**
|
|
6597
6839
|
* 사용자 ID로 모든 권한 오버라이드 조회
|
|
6598
6840
|
*/
|
|
6599
6841
|
async findByUserId(userId) {
|
|
6600
|
-
return this.readDb.select().from(userPermissions).where(
|
|
6842
|
+
return this.readDb.select().from(userPermissions).where(eq9(userPermissions.userId, userId));
|
|
6601
6843
|
}
|
|
6602
6844
|
/**
|
|
6603
6845
|
* 사용자 ID로 유효한 권한 오버라이드만 조회
|
|
@@ -6606,11 +6848,11 @@ var init_user_permissions_repository = __esm({
|
|
|
6606
6848
|
async findValidByUserId(userId) {
|
|
6607
6849
|
const now = /* @__PURE__ */ new Date();
|
|
6608
6850
|
return this.readDb.select().from(userPermissions).where(
|
|
6609
|
-
|
|
6610
|
-
|
|
6851
|
+
and7(
|
|
6852
|
+
eq9(userPermissions.userId, userId),
|
|
6611
6853
|
or2(
|
|
6612
6854
|
isNull4(userPermissions.expiresAt),
|
|
6613
|
-
|
|
6855
|
+
gt3(userPermissions.expiresAt, now)
|
|
6614
6856
|
)
|
|
6615
6857
|
)
|
|
6616
6858
|
);
|
|
@@ -6620,9 +6862,9 @@ var init_user_permissions_repository = __esm({
|
|
|
6620
6862
|
*/
|
|
6621
6863
|
async findByUserIdAndPermissionId(userId, permissionId) {
|
|
6622
6864
|
const result = await this.readDb.select().from(userPermissions).where(
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6865
|
+
and7(
|
|
6866
|
+
eq9(userPermissions.userId, userId),
|
|
6867
|
+
eq9(userPermissions.permissionId, permissionId)
|
|
6626
6868
|
)
|
|
6627
6869
|
).limit(1);
|
|
6628
6870
|
return result[0] ?? null;
|
|
@@ -6636,8 +6878,8 @@ var init_user_permissions_repository = __esm({
|
|
|
6636
6878
|
/**
|
|
6637
6879
|
* 사용자 권한 오버라이드 업데이트
|
|
6638
6880
|
*/
|
|
6639
|
-
async updateById(
|
|
6640
|
-
const result = await this.db.update(userPermissions).set(data).where(
|
|
6881
|
+
async updateById(id15, data) {
|
|
6882
|
+
const result = await this.db.update(userPermissions).set(data).where(eq9(userPermissions.id, id15)).returning();
|
|
6641
6883
|
return result[0] ?? null;
|
|
6642
6884
|
}
|
|
6643
6885
|
/**
|
|
@@ -6645,9 +6887,9 @@ var init_user_permissions_repository = __esm({
|
|
|
6645
6887
|
*/
|
|
6646
6888
|
async deleteByUserIdAndPermissionId(userId, permissionId) {
|
|
6647
6889
|
const result = await this.db.delete(userPermissions).where(
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6890
|
+
and7(
|
|
6891
|
+
eq9(userPermissions.userId, userId),
|
|
6892
|
+
eq9(userPermissions.permissionId, permissionId)
|
|
6651
6893
|
)
|
|
6652
6894
|
).returning();
|
|
6653
6895
|
return result[0] ?? null;
|
|
@@ -6656,7 +6898,7 @@ var init_user_permissions_repository = __esm({
|
|
|
6656
6898
|
* 사용자의 모든 권한 오버라이드 삭제
|
|
6657
6899
|
*/
|
|
6658
6900
|
async deleteByUserId(userId) {
|
|
6659
|
-
const result = await this.db.delete(userPermissions).where(
|
|
6901
|
+
const result = await this.db.delete(userPermissions).where(eq9(userPermissions.userId, userId)).returning();
|
|
6660
6902
|
return result.length;
|
|
6661
6903
|
}
|
|
6662
6904
|
/**
|
|
@@ -6665,7 +6907,7 @@ var init_user_permissions_repository = __esm({
|
|
|
6665
6907
|
async deleteExpired() {
|
|
6666
6908
|
const now = /* @__PURE__ */ new Date();
|
|
6667
6909
|
const result = await this.db.delete(userPermissions).where(
|
|
6668
|
-
|
|
6910
|
+
and7(
|
|
6669
6911
|
isNotNull(userPermissions.expiresAt),
|
|
6670
6912
|
lt3(userPermissions.expiresAt, now)
|
|
6671
6913
|
)
|
|
@@ -6678,33 +6920,33 @@ var init_user_permissions_repository = __esm({
|
|
|
6678
6920
|
});
|
|
6679
6921
|
|
|
6680
6922
|
// src/server/repositories/user-profiles.repository.ts
|
|
6681
|
-
import { BaseRepository as
|
|
6682
|
-
import { eq as
|
|
6923
|
+
import { BaseRepository as BaseRepository10 } from "@spfn/core/db";
|
|
6924
|
+
import { eq as eq10 } from "drizzle-orm";
|
|
6683
6925
|
var UserProfilesRepository, userProfilesRepository;
|
|
6684
6926
|
var init_user_profiles_repository = __esm({
|
|
6685
6927
|
"src/server/repositories/user-profiles.repository.ts"() {
|
|
6686
6928
|
"use strict";
|
|
6687
6929
|
init_user_profiles();
|
|
6688
|
-
UserProfilesRepository = class extends
|
|
6930
|
+
UserProfilesRepository = class extends BaseRepository10 {
|
|
6689
6931
|
/**
|
|
6690
6932
|
* ID로 프로필 조회
|
|
6691
6933
|
*/
|
|
6692
|
-
async findById(
|
|
6693
|
-
const result = await this.readDb.select().from(userProfiles).where(
|
|
6934
|
+
async findById(id15) {
|
|
6935
|
+
const result = await this.readDb.select().from(userProfiles).where(eq10(userProfiles.id, id15)).limit(1);
|
|
6694
6936
|
return result[0] ?? null;
|
|
6695
6937
|
}
|
|
6696
6938
|
/**
|
|
6697
6939
|
* User ID로 locale만 조회 (경량)
|
|
6698
6940
|
*/
|
|
6699
6941
|
async findLocaleByUserId(userId) {
|
|
6700
|
-
const result = await this.readDb.select({ locale: userProfiles.locale }).from(userProfiles).where(
|
|
6942
|
+
const result = await this.readDb.select({ locale: userProfiles.locale }).from(userProfiles).where(eq10(userProfiles.userId, userId)).limit(1);
|
|
6701
6943
|
return result[0]?.locale || "en";
|
|
6702
6944
|
}
|
|
6703
6945
|
/**
|
|
6704
6946
|
* User ID로 프로필 조회
|
|
6705
6947
|
*/
|
|
6706
6948
|
async findByUserId(userId) {
|
|
6707
|
-
const result = await this.readDb.select().from(userProfiles).where(
|
|
6949
|
+
const result = await this.readDb.select().from(userProfiles).where(eq10(userProfiles.userId, userId)).limit(1);
|
|
6708
6950
|
return result[0] ?? null;
|
|
6709
6951
|
}
|
|
6710
6952
|
/**
|
|
@@ -6716,29 +6958,29 @@ var init_user_profiles_repository = __esm({
|
|
|
6716
6958
|
/**
|
|
6717
6959
|
* 프로필 업데이트 (by ID)
|
|
6718
6960
|
*/
|
|
6719
|
-
async updateById(
|
|
6720
|
-
const result = await this.db.update(userProfiles).set(data).where(
|
|
6961
|
+
async updateById(id15, data) {
|
|
6962
|
+
const result = await this.db.update(userProfiles).set(data).where(eq10(userProfiles.id, id15)).returning();
|
|
6721
6963
|
return result[0] ?? null;
|
|
6722
6964
|
}
|
|
6723
6965
|
/**
|
|
6724
6966
|
* 프로필 업데이트 (by User ID)
|
|
6725
6967
|
*/
|
|
6726
6968
|
async updateByUserId(userId, data) {
|
|
6727
|
-
const result = await this.db.update(userProfiles).set(data).where(
|
|
6969
|
+
const result = await this.db.update(userProfiles).set(data).where(eq10(userProfiles.userId, userId)).returning();
|
|
6728
6970
|
return result[0] ?? null;
|
|
6729
6971
|
}
|
|
6730
6972
|
/**
|
|
6731
6973
|
* 프로필 삭제 (by ID)
|
|
6732
6974
|
*/
|
|
6733
|
-
async deleteById(
|
|
6734
|
-
const result = await this.db.delete(userProfiles).where(
|
|
6975
|
+
async deleteById(id15) {
|
|
6976
|
+
const result = await this.db.delete(userProfiles).where(eq10(userProfiles.id, id15)).returning();
|
|
6735
6977
|
return result[0] ?? null;
|
|
6736
6978
|
}
|
|
6737
6979
|
/**
|
|
6738
6980
|
* 프로필 삭제 (by User ID)
|
|
6739
6981
|
*/
|
|
6740
6982
|
async deleteByUserId(userId) {
|
|
6741
|
-
const result = await this.db.delete(userProfiles).where(
|
|
6983
|
+
const result = await this.db.delete(userProfiles).where(eq10(userProfiles.userId, userId)).returning();
|
|
6742
6984
|
return result[0] ?? null;
|
|
6743
6985
|
}
|
|
6744
6986
|
/**
|
|
@@ -6780,7 +7022,7 @@ var init_user_profiles_repository = __esm({
|
|
|
6780
7022
|
metadata: userProfiles.metadata,
|
|
6781
7023
|
createdAt: userProfiles.createdAt,
|
|
6782
7024
|
updatedAt: userProfiles.updatedAt
|
|
6783
|
-
}).from(userProfiles).where(
|
|
7025
|
+
}).from(userProfiles).where(eq10(userProfiles.userId, userId)).limit(1).then((rows) => rows[0] ?? null);
|
|
6784
7026
|
if (!profile) {
|
|
6785
7027
|
return null;
|
|
6786
7028
|
}
|
|
@@ -6808,8 +7050,8 @@ var init_user_profiles_repository = __esm({
|
|
|
6808
7050
|
});
|
|
6809
7051
|
|
|
6810
7052
|
// src/server/repositories/invitations.repository.ts
|
|
6811
|
-
import { eq as
|
|
6812
|
-
import { BaseRepository as
|
|
7053
|
+
import { eq as eq11, and as and8, lt as lt4, desc as desc2, sql as sql7 } from "drizzle-orm";
|
|
7054
|
+
import { BaseRepository as BaseRepository11 } from "@spfn/core/db";
|
|
6813
7055
|
var InvitationsRepository, invitationsRepository;
|
|
6814
7056
|
var init_invitations_repository = __esm({
|
|
6815
7057
|
"src/server/repositories/invitations.repository.ts"() {
|
|
@@ -6818,19 +7060,19 @@ var init_invitations_repository = __esm({
|
|
|
6818
7060
|
init_roles();
|
|
6819
7061
|
init_user_invitations();
|
|
6820
7062
|
init_email();
|
|
6821
|
-
InvitationsRepository = class extends
|
|
7063
|
+
InvitationsRepository = class extends BaseRepository11 {
|
|
6822
7064
|
/**
|
|
6823
7065
|
* ID로 초대 조회
|
|
6824
7066
|
*/
|
|
6825
|
-
async findById(
|
|
6826
|
-
const result = await this.readDb.select().from(userInvitations).where(
|
|
7067
|
+
async findById(id15) {
|
|
7068
|
+
const result = await this.readDb.select().from(userInvitations).where(eq11(userInvitations.id, id15)).limit(1);
|
|
6827
7069
|
return result[0] ?? null;
|
|
6828
7070
|
}
|
|
6829
7071
|
/**
|
|
6830
7072
|
* Token으로 초대 조회
|
|
6831
7073
|
*/
|
|
6832
7074
|
async findByToken(token) {
|
|
6833
|
-
const result = await this.readDb.select().from(userInvitations).where(
|
|
7075
|
+
const result = await this.readDb.select().from(userInvitations).where(eq11(userInvitations.token, token)).limit(1);
|
|
6834
7076
|
return result[0] ?? null;
|
|
6835
7077
|
}
|
|
6836
7078
|
/**
|
|
@@ -6838,9 +7080,9 @@ var init_invitations_repository = __esm({
|
|
|
6838
7080
|
*/
|
|
6839
7081
|
async findPendingByEmail(email) {
|
|
6840
7082
|
const result = await this.readDb.select().from(userInvitations).where(
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
7083
|
+
and8(
|
|
7084
|
+
eq11(userInvitations.email, normalizeEmail(email)),
|
|
7085
|
+
eq11(userInvitations.status, "pending")
|
|
6844
7086
|
)
|
|
6845
7087
|
).limit(1);
|
|
6846
7088
|
return result[0] ?? null;
|
|
@@ -6849,13 +7091,13 @@ var init_invitations_repository = __esm({
|
|
|
6849
7091
|
* 초대자 ID로 모든 초대 조회
|
|
6850
7092
|
*/
|
|
6851
7093
|
async findByInvitedBy(invitedBy) {
|
|
6852
|
-
return this.readDb.select().from(userInvitations).where(
|
|
7094
|
+
return this.readDb.select().from(userInvitations).where(eq11(userInvitations.invitedBy, invitedBy));
|
|
6853
7095
|
}
|
|
6854
7096
|
/**
|
|
6855
7097
|
* 상태별 초대 조회
|
|
6856
7098
|
*/
|
|
6857
7099
|
async findByStatus(status) {
|
|
6858
|
-
return this.readDb.select().from(userInvitations).where(
|
|
7100
|
+
return this.readDb.select().from(userInvitations).where(eq11(userInvitations.status, status));
|
|
6859
7101
|
}
|
|
6860
7102
|
/**
|
|
6861
7103
|
* 초대 생성
|
|
@@ -6866,7 +7108,7 @@ var init_invitations_repository = __esm({
|
|
|
6866
7108
|
/**
|
|
6867
7109
|
* 초대 상태 업데이트
|
|
6868
7110
|
*/
|
|
6869
|
-
async updateStatus(
|
|
7111
|
+
async updateStatus(id15, status, timestamp2) {
|
|
6870
7112
|
const updates = {
|
|
6871
7113
|
status
|
|
6872
7114
|
};
|
|
@@ -6877,14 +7119,14 @@ var init_invitations_repository = __esm({
|
|
|
6877
7119
|
updates.cancelledAt = timestamp2;
|
|
6878
7120
|
}
|
|
6879
7121
|
}
|
|
6880
|
-
const result = await this.db.update(userInvitations).set(updates).where(
|
|
7122
|
+
const result = await this.db.update(userInvitations).set(updates).where(eq11(userInvitations.id, id15)).returning();
|
|
6881
7123
|
return result[0] ?? null;
|
|
6882
7124
|
}
|
|
6883
7125
|
/**
|
|
6884
7126
|
* 초대 삭제
|
|
6885
7127
|
*/
|
|
6886
|
-
async deleteById(
|
|
6887
|
-
const result = await this.db.delete(userInvitations).where(
|
|
7128
|
+
async deleteById(id15) {
|
|
7129
|
+
const result = await this.db.delete(userInvitations).where(eq11(userInvitations.id, id15)).returning();
|
|
6888
7130
|
return result[0] ?? null;
|
|
6889
7131
|
}
|
|
6890
7132
|
/**
|
|
@@ -6893,8 +7135,8 @@ var init_invitations_repository = __esm({
|
|
|
6893
7135
|
async updateExpiredInvitations() {
|
|
6894
7136
|
const now = /* @__PURE__ */ new Date();
|
|
6895
7137
|
const result = await this.db.update(userInvitations).set({ status: "expired" }).where(
|
|
6896
|
-
|
|
6897
|
-
|
|
7138
|
+
and8(
|
|
7139
|
+
eq11(userInvitations.status, "pending"),
|
|
6898
7140
|
lt4(userInvitations.expiresAt, now)
|
|
6899
7141
|
)
|
|
6900
7142
|
).returning();
|
|
@@ -6926,7 +7168,7 @@ var init_invitations_repository = __esm({
|
|
|
6926
7168
|
id: users.id,
|
|
6927
7169
|
email: users.email
|
|
6928
7170
|
}
|
|
6929
|
-
}).from(userInvitations).innerJoin(roles,
|
|
7171
|
+
}).from(userInvitations).innerJoin(roles, eq11(userInvitations.roleId, roles.id)).innerJoin(users, eq11(userInvitations.invitedBy, users.id)).where(eq11(userInvitations.token, token)).limit(1);
|
|
6930
7172
|
return result[0] ?? null;
|
|
6931
7173
|
}
|
|
6932
7174
|
/**
|
|
@@ -6937,13 +7179,13 @@ var init_invitations_repository = __esm({
|
|
|
6937
7179
|
const offset = (page - 1) * limit;
|
|
6938
7180
|
const conditions = [];
|
|
6939
7181
|
if (status) {
|
|
6940
|
-
conditions.push(
|
|
7182
|
+
conditions.push(eq11(userInvitations.status, status));
|
|
6941
7183
|
}
|
|
6942
7184
|
if (invitedBy) {
|
|
6943
|
-
conditions.push(
|
|
7185
|
+
conditions.push(eq11(userInvitations.invitedBy, invitedBy));
|
|
6944
7186
|
}
|
|
6945
|
-
const whereClause = conditions.length > 0 ?
|
|
6946
|
-
const countResult = await this.readDb.select({ count:
|
|
7187
|
+
const whereClause = conditions.length > 0 ? and8(...conditions) : void 0;
|
|
7188
|
+
const countResult = await this.readDb.select({ count: sql7`count(*)` }).from(userInvitations).where(whereClause);
|
|
6947
7189
|
const total = Number(countResult[0]?.count || 0);
|
|
6948
7190
|
const results = await this.readDb.select({
|
|
6949
7191
|
id: userInvitations.id,
|
|
@@ -6967,7 +7209,7 @@ var init_invitations_repository = __esm({
|
|
|
6967
7209
|
id: users.id,
|
|
6968
7210
|
email: users.email
|
|
6969
7211
|
}
|
|
6970
|
-
}).from(userInvitations).innerJoin(roles,
|
|
7212
|
+
}).from(userInvitations).innerJoin(roles, eq11(userInvitations.roleId, roles.id)).innerJoin(users, eq11(userInvitations.invitedBy, users.id)).where(whereClause).orderBy(desc2(userInvitations.createdAt)).limit(limit).offset(offset);
|
|
6971
7213
|
return {
|
|
6972
7214
|
invitations: results,
|
|
6973
7215
|
total,
|
|
@@ -6979,31 +7221,31 @@ var init_invitations_repository = __esm({
|
|
|
6979
7221
|
/**
|
|
6980
7222
|
* 초대 업데이트 (일반 업데이트 - 모든 필드 가능)
|
|
6981
7223
|
*/
|
|
6982
|
-
async updateById(
|
|
7224
|
+
async updateById(id15, data) {
|
|
6983
7225
|
const patch = "email" in data && typeof data.email === "string" ? { ...data, email: normalizeEmail(data.email) } : data;
|
|
6984
|
-
const result = await this.db.update(userInvitations).set(patch).where(
|
|
7226
|
+
const result = await this.db.update(userInvitations).set(patch).where(eq11(userInvitations.id, id15)).returning();
|
|
6985
7227
|
return result[0] ?? null;
|
|
6986
7228
|
}
|
|
6987
7229
|
/**
|
|
6988
7230
|
* 초대 재전송 (status와 expiresAt 동시 업데이트)
|
|
6989
7231
|
*/
|
|
6990
|
-
async resend(
|
|
7232
|
+
async resend(id15, newExpiresAt) {
|
|
6991
7233
|
const result = await this.db.update(userInvitations).set({
|
|
6992
7234
|
status: "pending",
|
|
6993
7235
|
expiresAt: newExpiresAt
|
|
6994
|
-
}).where(
|
|
7236
|
+
}).where(eq11(userInvitations.id, id15)).returning();
|
|
6995
7237
|
return result[0] ?? null;
|
|
6996
7238
|
}
|
|
6997
7239
|
/**
|
|
6998
7240
|
* 초대 취소 (status, metadata 동시 업데이트)
|
|
6999
7241
|
*/
|
|
7000
|
-
async cancel(
|
|
7242
|
+
async cancel(id15, cancelledBy, reason, currentMetadata) {
|
|
7001
7243
|
const newMetadata = currentMetadata ? { ...currentMetadata, cancelReason: reason, cancelledBy } : { cancelReason: reason, cancelledBy };
|
|
7002
7244
|
const result = await this.db.update(userInvitations).set({
|
|
7003
7245
|
status: "cancelled",
|
|
7004
7246
|
cancelledAt: /* @__PURE__ */ new Date(),
|
|
7005
7247
|
metadata: newMetadata
|
|
7006
|
-
}).where(
|
|
7248
|
+
}).where(eq11(userInvitations.id, id15)).returning();
|
|
7007
7249
|
return result[0] ?? null;
|
|
7008
7250
|
}
|
|
7009
7251
|
};
|
|
@@ -7090,6 +7332,15 @@ var init_schema5 = __esm({
|
|
|
7090
7332
|
examples: [true, false]
|
|
7091
7333
|
})
|
|
7092
7334
|
},
|
|
7335
|
+
SPFN_AUTH_CSRF: {
|
|
7336
|
+
...envString({
|
|
7337
|
+
description: 'CSRF protection for cookie-session mutations in the Next.js proxy: off | warn | enforce. Unset behaves as "warn" (log what would be refused, allow it through). configureAuth({ csrf: { mode } }) takes precedence.',
|
|
7338
|
+
required: false,
|
|
7339
|
+
nextjs: true,
|
|
7340
|
+
// The check runs in the Next.js proxy
|
|
7341
|
+
examples: ["enforce", "warn", "off"]
|
|
7342
|
+
})
|
|
7343
|
+
},
|
|
7093
7344
|
SPFN_AUTH_BCRYPT_SALT_ROUNDS: {
|
|
7094
7345
|
...envNumber({
|
|
7095
7346
|
description: "Bcrypt salt rounds (cost factor, higher = more secure but slower)",
|
|
@@ -7650,15 +7901,15 @@ var init_token_cipher = __esm({
|
|
|
7650
7901
|
});
|
|
7651
7902
|
|
|
7652
7903
|
// src/server/repositories/social-accounts.repository.ts
|
|
7653
|
-
import { eq as
|
|
7654
|
-
import { BaseRepository as
|
|
7904
|
+
import { eq as eq12, and as and9 } from "drizzle-orm";
|
|
7905
|
+
import { BaseRepository as BaseRepository12 } from "@spfn/core/db";
|
|
7655
7906
|
var SocialAccountsRepository, socialAccountsRepository;
|
|
7656
7907
|
var init_social_accounts_repository = __esm({
|
|
7657
7908
|
"src/server/repositories/social-accounts.repository.ts"() {
|
|
7658
7909
|
"use strict";
|
|
7659
7910
|
init_entities();
|
|
7660
7911
|
init_token_cipher();
|
|
7661
|
-
SocialAccountsRepository = class extends
|
|
7912
|
+
SocialAccountsRepository = class extends BaseRepository12 {
|
|
7662
7913
|
/**
|
|
7663
7914
|
* 저장 row 의 토큰을 평문으로 복호화해 반환한다.
|
|
7664
7915
|
*
|
|
@@ -7686,10 +7937,10 @@ var init_social_accounts_repository = __esm({
|
|
|
7686
7937
|
if (refresh?.needsRotation) {
|
|
7687
7938
|
heal.refreshToken = await encryptToken(refresh.value, context("refresh"));
|
|
7688
7939
|
}
|
|
7689
|
-
await this.db.update(userSocialAccounts).set(heal).where(
|
|
7690
|
-
|
|
7691
|
-
access?.needsRotation && account.accessToken !== null ?
|
|
7692
|
-
refresh?.needsRotation && account.refreshToken !== null ?
|
|
7940
|
+
await this.db.update(userSocialAccounts).set(heal).where(and9(
|
|
7941
|
+
eq12(userSocialAccounts.id, account.id),
|
|
7942
|
+
access?.needsRotation && account.accessToken !== null ? eq12(userSocialAccounts.accessToken, account.accessToken) : void 0,
|
|
7943
|
+
refresh?.needsRotation && account.refreshToken !== null ? eq12(userSocialAccounts.refreshToken, account.refreshToken) : void 0
|
|
7693
7944
|
));
|
|
7694
7945
|
} catch {
|
|
7695
7946
|
}
|
|
@@ -7706,9 +7957,9 @@ var init_social_accounts_repository = __esm({
|
|
|
7706
7957
|
*/
|
|
7707
7958
|
async findByProviderAndProviderId(provider, providerUserId) {
|
|
7708
7959
|
const result = await this.readDb.select().from(userSocialAccounts).where(
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7960
|
+
and9(
|
|
7961
|
+
eq12(userSocialAccounts.provider, provider),
|
|
7962
|
+
eq12(userSocialAccounts.providerUserId, providerUserId)
|
|
7712
7963
|
)
|
|
7713
7964
|
).limit(1);
|
|
7714
7965
|
return this.decryptAccount(result[0] ?? null);
|
|
@@ -7718,7 +7969,7 @@ var init_social_accounts_repository = __esm({
|
|
|
7718
7969
|
* Read replica 사용
|
|
7719
7970
|
*/
|
|
7720
7971
|
async findByUserId(userId) {
|
|
7721
|
-
const result = await this.readDb.select().from(userSocialAccounts).where(
|
|
7972
|
+
const result = await this.readDb.select().from(userSocialAccounts).where(eq12(userSocialAccounts.userId, userId));
|
|
7722
7973
|
return Promise.all(result.map((account) => this.decryptAccount(account)));
|
|
7723
7974
|
}
|
|
7724
7975
|
/**
|
|
@@ -7727,9 +7978,9 @@ var init_social_accounts_repository = __esm({
|
|
|
7727
7978
|
*/
|
|
7728
7979
|
async findByUserIdAndProvider(userId, provider) {
|
|
7729
7980
|
const result = await this.readDb.select().from(userSocialAccounts).where(
|
|
7730
|
-
|
|
7731
|
-
|
|
7732
|
-
|
|
7981
|
+
and9(
|
|
7982
|
+
eq12(userSocialAccounts.userId, userId),
|
|
7983
|
+
eq12(userSocialAccounts.provider, provider)
|
|
7733
7984
|
)
|
|
7734
7985
|
).limit(1);
|
|
7735
7986
|
return this.decryptAccount(result[0] ?? null);
|
|
@@ -7755,11 +8006,11 @@ var init_social_accounts_repository = __esm({
|
|
|
7755
8006
|
* 토큰 정보 업데이트
|
|
7756
8007
|
* Write primary 사용
|
|
7757
8008
|
*/
|
|
7758
|
-
async updateTokens(
|
|
8009
|
+
async updateTokens(id15, data) {
|
|
7759
8010
|
const accounts = await this.db.select({
|
|
7760
8011
|
provider: userSocialAccounts.provider,
|
|
7761
8012
|
providerUserId: userSocialAccounts.providerUserId
|
|
7762
|
-
}).from(userSocialAccounts).where(
|
|
8013
|
+
}).from(userSocialAccounts).where(eq12(userSocialAccounts.id, id15)).limit(1);
|
|
7763
8014
|
const account = accounts[0];
|
|
7764
8015
|
if (!account) {
|
|
7765
8016
|
return null;
|
|
@@ -7773,15 +8024,15 @@ var init_social_accounts_repository = __esm({
|
|
|
7773
8024
|
...data,
|
|
7774
8025
|
accessToken: data.accessToken ? await encryptToken(data.accessToken, context("access")) : data.accessToken,
|
|
7775
8026
|
refreshToken: data.refreshToken ? await encryptToken(data.refreshToken, context("refresh")) : data.refreshToken
|
|
7776
|
-
}).where(
|
|
8027
|
+
}).where(eq12(userSocialAccounts.id, id15)).returning();
|
|
7777
8028
|
return this.decryptAccount(result[0] ?? null);
|
|
7778
8029
|
}
|
|
7779
8030
|
/**
|
|
7780
8031
|
* 소셜 계정 삭제
|
|
7781
8032
|
* Write primary 사용
|
|
7782
8033
|
*/
|
|
7783
|
-
async deleteById(
|
|
7784
|
-
const result = await this.db.delete(userSocialAccounts).where(
|
|
8034
|
+
async deleteById(id15) {
|
|
8035
|
+
const result = await this.db.delete(userSocialAccounts).where(eq12(userSocialAccounts.id, id15)).returning();
|
|
7785
8036
|
return result[0] ?? null;
|
|
7786
8037
|
}
|
|
7787
8038
|
/**
|
|
@@ -7790,9 +8041,9 @@ var init_social_accounts_repository = __esm({
|
|
|
7790
8041
|
*/
|
|
7791
8042
|
async deleteByUserIdAndProvider(userId, provider) {
|
|
7792
8043
|
const result = await this.db.delete(userSocialAccounts).where(
|
|
7793
|
-
|
|
7794
|
-
|
|
7795
|
-
|
|
8044
|
+
and9(
|
|
8045
|
+
eq12(userSocialAccounts.userId, userId),
|
|
8046
|
+
eq12(userSocialAccounts.provider, provider)
|
|
7796
8047
|
)
|
|
7797
8048
|
).returning();
|
|
7798
8049
|
return result[0] ?? null;
|
|
@@ -7805,7 +8056,7 @@ var init_social_accounts_repository = __esm({
|
|
|
7805
8056
|
* Write primary 사용
|
|
7806
8057
|
*/
|
|
7807
8058
|
async deleteAllByUserId(userId) {
|
|
7808
|
-
const result = await this.db.delete(userSocialAccounts).where(
|
|
8059
|
+
const result = await this.db.delete(userSocialAccounts).where(eq12(userSocialAccounts.userId, userId)).returning();
|
|
7809
8060
|
return result.length;
|
|
7810
8061
|
}
|
|
7811
8062
|
};
|
|
@@ -7814,19 +8065,19 @@ var init_social_accounts_repository = __esm({
|
|
|
7814
8065
|
});
|
|
7815
8066
|
|
|
7816
8067
|
// src/server/repositories/auth-metadata.repository.ts
|
|
7817
|
-
import { BaseRepository as
|
|
7818
|
-
import { eq as
|
|
8068
|
+
import { BaseRepository as BaseRepository13 } from "@spfn/core/db";
|
|
8069
|
+
import { eq as eq13 } from "drizzle-orm";
|
|
7819
8070
|
var AuthMetadataRepository, authMetadataRepository;
|
|
7820
8071
|
var init_auth_metadata_repository = __esm({
|
|
7821
8072
|
"src/server/repositories/auth-metadata.repository.ts"() {
|
|
7822
8073
|
"use strict";
|
|
7823
8074
|
init_auth_metadata();
|
|
7824
|
-
AuthMetadataRepository = class extends
|
|
8075
|
+
AuthMetadataRepository = class extends BaseRepository13 {
|
|
7825
8076
|
/**
|
|
7826
8077
|
* 키로 값 조회
|
|
7827
8078
|
*/
|
|
7828
8079
|
async get(key) {
|
|
7829
|
-
const result = await this.readDb.select().from(authMetadata).where(
|
|
8080
|
+
const result = await this.readDb.select().from(authMetadata).where(eq13(authMetadata.key, key)).limit(1);
|
|
7830
8081
|
return result[0]?.value ?? null;
|
|
7831
8082
|
}
|
|
7832
8083
|
/**
|
|
@@ -7849,20 +8100,20 @@ var init_auth_metadata_repository = __esm({
|
|
|
7849
8100
|
});
|
|
7850
8101
|
|
|
7851
8102
|
// src/server/repositories/account-deletion-requests.repository.ts
|
|
7852
|
-
import { eq as
|
|
7853
|
-
import { BaseRepository as
|
|
8103
|
+
import { eq as eq14, and as and10, lte } from "drizzle-orm";
|
|
8104
|
+
import { BaseRepository as BaseRepository14 } from "@spfn/core/db";
|
|
7854
8105
|
var AccountDeletionRequestsRepository, accountDeletionRequestsRepository;
|
|
7855
8106
|
var init_account_deletion_requests_repository = __esm({
|
|
7856
8107
|
"src/server/repositories/account-deletion-requests.repository.ts"() {
|
|
7857
8108
|
"use strict";
|
|
7858
8109
|
init_account_deletion_requests();
|
|
7859
|
-
AccountDeletionRequestsRepository = class extends
|
|
8110
|
+
AccountDeletionRequestsRepository = class extends BaseRepository14 {
|
|
7860
8111
|
/**
|
|
7861
8112
|
* ID로 요청 조회
|
|
7862
8113
|
* Read replica 사용
|
|
7863
8114
|
*/
|
|
7864
|
-
async findById(
|
|
7865
|
-
const result = await this.readDb.select().from(accountDeletionRequests).where(
|
|
8115
|
+
async findById(id15) {
|
|
8116
|
+
const result = await this.readDb.select().from(accountDeletionRequests).where(eq14(accountDeletionRequests.id, id15)).limit(1);
|
|
7866
8117
|
return result[0] ?? null;
|
|
7867
8118
|
}
|
|
7868
8119
|
/**
|
|
@@ -7871,9 +8122,9 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7871
8122
|
*/
|
|
7872
8123
|
async findPendingByUserId(userId) {
|
|
7873
8124
|
const result = await this.readDb.select().from(accountDeletionRequests).where(
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
8125
|
+
and10(
|
|
8126
|
+
eq14(accountDeletionRequests.userId, userId),
|
|
8127
|
+
eq14(accountDeletionRequests.status, "pending")
|
|
7877
8128
|
)
|
|
7878
8129
|
).limit(1);
|
|
7879
8130
|
return result[0] ?? null;
|
|
@@ -7887,9 +8138,9 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7887
8138
|
*/
|
|
7888
8139
|
async findPendingByUserIdOnPrimary(userId) {
|
|
7889
8140
|
const result = await this.db.select().from(accountDeletionRequests).where(
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
8141
|
+
and10(
|
|
8142
|
+
eq14(accountDeletionRequests.userId, userId),
|
|
8143
|
+
eq14(accountDeletionRequests.status, "pending")
|
|
7893
8144
|
)
|
|
7894
8145
|
).limit(1);
|
|
7895
8146
|
return result[0] ?? null;
|
|
@@ -7900,8 +8151,8 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7900
8151
|
*/
|
|
7901
8152
|
async findDueForPurge(now) {
|
|
7902
8153
|
return this.readDb.select().from(accountDeletionRequests).where(
|
|
7903
|
-
|
|
7904
|
-
|
|
8154
|
+
and10(
|
|
8155
|
+
eq14(accountDeletionRequests.status, "pending"),
|
|
7905
8156
|
lte(accountDeletionRequests.purgeScheduledAt, now)
|
|
7906
8157
|
)
|
|
7907
8158
|
);
|
|
@@ -7921,14 +8172,14 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7921
8172
|
* cancelled) 시 null을 반환하니 호출자가 그 결과를 확인해야 한다.
|
|
7922
8173
|
* Write primary 사용
|
|
7923
8174
|
*/
|
|
7924
|
-
async markCancelled(
|
|
8175
|
+
async markCancelled(id15) {
|
|
7925
8176
|
const result = await this.db.update(accountDeletionRequests).set({
|
|
7926
8177
|
status: "cancelled",
|
|
7927
8178
|
cancelledAt: /* @__PURE__ */ new Date()
|
|
7928
8179
|
}).where(
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
8180
|
+
and10(
|
|
8181
|
+
eq14(accountDeletionRequests.id, id15),
|
|
8182
|
+
eq14(accountDeletionRequests.status, "pending")
|
|
7932
8183
|
)
|
|
7933
8184
|
).returning();
|
|
7934
8185
|
return result[0] ?? null;
|
|
@@ -7943,15 +8194,15 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7943
8194
|
* destructive DML을 실행하기 **전에** 반드시 이 결과를 확인해야 한다.
|
|
7944
8195
|
* Write primary 사용
|
|
7945
8196
|
*/
|
|
7946
|
-
async markCompleted(
|
|
8197
|
+
async markCompleted(id15, purgeStrategy) {
|
|
7947
8198
|
const result = await this.db.update(accountDeletionRequests).set({
|
|
7948
8199
|
status: "completed",
|
|
7949
8200
|
completedAt: /* @__PURE__ */ new Date(),
|
|
7950
8201
|
purgeStrategy
|
|
7951
8202
|
}).where(
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
8203
|
+
and10(
|
|
8204
|
+
eq14(accountDeletionRequests.id, id15),
|
|
8205
|
+
eq14(accountDeletionRequests.status, "pending")
|
|
7955
8206
|
)
|
|
7956
8207
|
).returning();
|
|
7957
8208
|
return result[0] ?? null;
|
|
@@ -7962,14 +8213,14 @@ var init_account_deletion_requests_repository = __esm({
|
|
|
7962
8213
|
});
|
|
7963
8214
|
|
|
7964
8215
|
// src/server/repositories/ops-tokens.repository.ts
|
|
7965
|
-
import { and as
|
|
7966
|
-
import { BaseRepository as
|
|
8216
|
+
import { and as and11, desc as desc3, eq as eq15, isNull as isNull5 } from "drizzle-orm";
|
|
8217
|
+
import { BaseRepository as BaseRepository15 } from "@spfn/core/db";
|
|
7967
8218
|
var OpsTokensRepository, opsTokensRepository;
|
|
7968
8219
|
var init_ops_tokens_repository = __esm({
|
|
7969
8220
|
"src/server/repositories/ops-tokens.repository.ts"() {
|
|
7970
8221
|
"use strict";
|
|
7971
8222
|
init_ops_tokens();
|
|
7972
|
-
OpsTokensRepository = class extends
|
|
8223
|
+
OpsTokensRepository = class extends BaseRepository15 {
|
|
7973
8224
|
/**
|
|
7974
8225
|
* Lookup by the secret's hash — the verification path.
|
|
7975
8226
|
*
|
|
@@ -7979,7 +8230,7 @@ var init_ops_tokens_repository = __esm({
|
|
|
7979
8230
|
* and revocation is documented as taking effect immediately.
|
|
7980
8231
|
*/
|
|
7981
8232
|
async findByTokenHash(tokenHash) {
|
|
7982
|
-
const result = await this.db.select().from(opsTokens).where(
|
|
8233
|
+
const result = await this.db.select().from(opsTokens).where(eq15(opsTokens.tokenHash, tokenHash)).limit(1);
|
|
7983
8234
|
return result[0] ?? null;
|
|
7984
8235
|
}
|
|
7985
8236
|
async create(data) {
|
|
@@ -7994,13 +8245,13 @@ var init_ops_tokens_repository = __esm({
|
|
|
7994
8245
|
* token is already revoked — the first revocation's timestamp is never
|
|
7995
8246
|
* overwritten.
|
|
7996
8247
|
*/
|
|
7997
|
-
async revokeById(
|
|
7998
|
-
const result = await this.db.update(opsTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(
|
|
8248
|
+
async revokeById(id15) {
|
|
8249
|
+
const result = await this.db.update(opsTokens).set({ revokedAt: /* @__PURE__ */ new Date() }).where(and11(eq15(opsTokens.id, id15), isNull5(opsTokens.revokedAt))).returning();
|
|
7999
8250
|
return result[0] ?? null;
|
|
8000
8251
|
}
|
|
8001
8252
|
/** Fire-and-forget from the verification path. */
|
|
8002
|
-
async updateLastUsedById(
|
|
8003
|
-
await this.db.update(opsTokens).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(
|
|
8253
|
+
async updateLastUsedById(id15) {
|
|
8254
|
+
await this.db.update(opsTokens).set({ lastUsedAt: /* @__PURE__ */ new Date() }).where(eq15(opsTokens.id, id15));
|
|
8004
8255
|
}
|
|
8005
8256
|
};
|
|
8006
8257
|
opsTokensRepository = new OpsTokensRepository();
|
|
@@ -8015,6 +8266,7 @@ var init_repositories = __esm({
|
|
|
8015
8266
|
init_keys_repository();
|
|
8016
8267
|
init_verification_codes_repository();
|
|
8017
8268
|
init_signup_link_tokens_repository();
|
|
8269
|
+
init_device_authorizations_repository();
|
|
8018
8270
|
init_roles_repository();
|
|
8019
8271
|
init_permissions_repository();
|
|
8020
8272
|
init_role_permissions_repository();
|
|
@@ -8116,7 +8368,7 @@ async function removePermissionFromRole(roleId, permissionId) {
|
|
|
8116
8368
|
}
|
|
8117
8369
|
async function setRolePermissions(roleId, permissionIds) {
|
|
8118
8370
|
const roleIdNum = Number(roleId);
|
|
8119
|
-
const permissionIdNums = permissionIds.map((
|
|
8371
|
+
const permissionIdNums = permissionIds.map((id15) => Number(id15));
|
|
8120
8372
|
await rolePermissionsRepository.setPermissionsForRole(roleIdNum, permissionIdNums);
|
|
8121
8373
|
}
|
|
8122
8374
|
async function getAllRoles(includeInactive = false) {
|
|
@@ -8136,7 +8388,7 @@ async function getRolePermissions(roleId) {
|
|
|
8136
8388
|
}
|
|
8137
8389
|
const permissionIds = mappings.map((m) => m.permissionId);
|
|
8138
8390
|
const perms = await Promise.all(
|
|
8139
|
-
permissionIds.map((
|
|
8391
|
+
permissionIds.map((id15) => permissionsRepository.findById(id15))
|
|
8140
8392
|
);
|
|
8141
8393
|
return perms.filter((p) => p !== null).map((p) => p.name);
|
|
8142
8394
|
}
|
|
@@ -8353,6 +8605,27 @@ import {
|
|
|
8353
8605
|
// src/server/lib/config.ts
|
|
8354
8606
|
init_email();
|
|
8355
8607
|
import { env as env4 } from "@spfn/auth/config";
|
|
8608
|
+
|
|
8609
|
+
// src/server/logger.ts
|
|
8610
|
+
import { logger as rootLogger } from "@spfn/core/logger";
|
|
8611
|
+
var authLogger = {
|
|
8612
|
+
plugin: rootLogger.child("@spfn/auth:plugin"),
|
|
8613
|
+
middleware: rootLogger.child("@spfn/auth:middleware"),
|
|
8614
|
+
interceptor: {
|
|
8615
|
+
general: rootLogger.child("@spfn/auth:interceptor:general"),
|
|
8616
|
+
login: rootLogger.child("@spfn/auth:interceptor:login"),
|
|
8617
|
+
keyRotation: rootLogger.child("@spfn/auth:interceptor:key-rotation"),
|
|
8618
|
+
oauth: rootLogger.child("@spfn/auth:interceptor:oauth"),
|
|
8619
|
+
csrf: rootLogger.child("@spfn/auth:interceptor:csrf")
|
|
8620
|
+
},
|
|
8621
|
+
session: rootLogger.child("@spfn/auth:session"),
|
|
8622
|
+
service: rootLogger.child("@spfn/auth:service"),
|
|
8623
|
+
setup: rootLogger.child("@spfn/auth:setup"),
|
|
8624
|
+
email: rootLogger.child("@spfn/auth:email"),
|
|
8625
|
+
sms: rootLogger.child("@spfn/auth:sms")
|
|
8626
|
+
};
|
|
8627
|
+
|
|
8628
|
+
// src/server/lib/config.ts
|
|
8356
8629
|
function getCookieSuffix() {
|
|
8357
8630
|
const port = process.env.SPFN_PORT;
|
|
8358
8631
|
return port ? `_${port}` : "";
|
|
@@ -8377,6 +8650,10 @@ var COOKIE_NAMES = {
|
|
|
8377
8650
|
/** Password-setup session for verified-email signup — temporary, single-purpose */
|
|
8378
8651
|
get SIGNUP_SETUP() {
|
|
8379
8652
|
return `spfn_signup_setup${getCookieSuffix()}`;
|
|
8653
|
+
},
|
|
8654
|
+
/** CSRF token — the only cookie here the browser can read */
|
|
8655
|
+
get CSRF() {
|
|
8656
|
+
return `spfn_csrf${getCookieSuffix()}`;
|
|
8380
8657
|
}
|
|
8381
8658
|
};
|
|
8382
8659
|
function matchOAuthCsrfCookies(cookies) {
|
|
@@ -8409,10 +8686,10 @@ var globalConfig = {
|
|
|
8409
8686
|
sessionTtl: "7d"
|
|
8410
8687
|
// Default: 7 days
|
|
8411
8688
|
};
|
|
8412
|
-
function configureAuth(
|
|
8689
|
+
function configureAuth(config3) {
|
|
8413
8690
|
globalConfig = {
|
|
8414
8691
|
...globalConfig,
|
|
8415
|
-
...
|
|
8692
|
+
...config3
|
|
8416
8693
|
};
|
|
8417
8694
|
}
|
|
8418
8695
|
function getAuthConfig() {
|
|
@@ -8437,6 +8714,28 @@ function getSessionTtl(override) {
|
|
|
8437
8714
|
}
|
|
8438
8715
|
return 7 * 24 * 60 * 60;
|
|
8439
8716
|
}
|
|
8717
|
+
var CSRF_MODES = ["off", "warn", "enforce"];
|
|
8718
|
+
var unrecognizedCsrfModeReported = false;
|
|
8719
|
+
function getCsrfMode() {
|
|
8720
|
+
const configured2 = globalConfig.csrf?.mode ?? env4.SPFN_AUTH_CSRF;
|
|
8721
|
+
if (!configured2) {
|
|
8722
|
+
return "warn";
|
|
8723
|
+
}
|
|
8724
|
+
const normalized = String(configured2).trim().toLowerCase();
|
|
8725
|
+
if (!CSRF_MODES.includes(normalized)) {
|
|
8726
|
+
if (!unrecognizedCsrfModeReported) {
|
|
8727
|
+
unrecognizedCsrfModeReported = true;
|
|
8728
|
+
authLogger.interceptor.csrf.error(
|
|
8729
|
+
`Unrecognized CSRF mode "${configured2}" \u2014 expected off | warn | enforce. Enforcing.`
|
|
8730
|
+
);
|
|
8731
|
+
}
|
|
8732
|
+
return "enforce";
|
|
8733
|
+
}
|
|
8734
|
+
return normalized;
|
|
8735
|
+
}
|
|
8736
|
+
function getCsrfExemptPaths() {
|
|
8737
|
+
return globalConfig.csrf?.exemptPaths ?? [];
|
|
8738
|
+
}
|
|
8440
8739
|
|
|
8441
8740
|
// src/server/services/verification.service.ts
|
|
8442
8741
|
import crypto4 from "crypto";
|
|
@@ -8444,26 +8743,6 @@ import { env as env5 } from "@spfn/auth/config";
|
|
|
8444
8743
|
import { InvalidVerificationCodeError } from "@spfn/auth/errors";
|
|
8445
8744
|
import jwt2 from "jsonwebtoken";
|
|
8446
8745
|
import { sendEmail, sendSMS } from "@spfn/notification/server";
|
|
8447
|
-
|
|
8448
|
-
// src/server/logger.ts
|
|
8449
|
-
import { logger as rootLogger } from "@spfn/core/logger";
|
|
8450
|
-
var authLogger = {
|
|
8451
|
-
plugin: rootLogger.child("@spfn/auth:plugin"),
|
|
8452
|
-
middleware: rootLogger.child("@spfn/auth:middleware"),
|
|
8453
|
-
interceptor: {
|
|
8454
|
-
general: rootLogger.child("@spfn/auth:interceptor:general"),
|
|
8455
|
-
login: rootLogger.child("@spfn/auth:interceptor:login"),
|
|
8456
|
-
keyRotation: rootLogger.child("@spfn/auth:interceptor:key-rotation"),
|
|
8457
|
-
oauth: rootLogger.child("@spfn/auth:interceptor:oauth")
|
|
8458
|
-
},
|
|
8459
|
-
session: rootLogger.child("@spfn/auth:session"),
|
|
8460
|
-
service: rootLogger.child("@spfn/auth:service"),
|
|
8461
|
-
setup: rootLogger.child("@spfn/auth:setup"),
|
|
8462
|
-
email: rootLogger.child("@spfn/auth:email"),
|
|
8463
|
-
sms: rootLogger.child("@spfn/auth:sms")
|
|
8464
|
-
};
|
|
8465
|
-
|
|
8466
|
-
// src/server/services/verification.service.ts
|
|
8467
8746
|
init_email();
|
|
8468
8747
|
init_repositories();
|
|
8469
8748
|
var ACCOUNT_EXISTS_NOTICE_DEDUPE_MINUTES = 60;
|
|
@@ -8646,6 +8925,7 @@ var KEY_TTL_DAYS = 90;
|
|
|
8646
8925
|
init_repositories();
|
|
8647
8926
|
import { InvalidKeyFingerprintError, KeyIdAlreadyRegisteredError } from "@spfn/auth/errors";
|
|
8648
8927
|
var KEY_FINGERPRINT_PREFIX_LENGTH = 8;
|
|
8928
|
+
var DEFAULT_KEY_ALGORITHM = "ES256";
|
|
8649
8929
|
function getKeyExpiryDate() {
|
|
8650
8930
|
const expiresAt = /* @__PURE__ */ new Date();
|
|
8651
8931
|
expiresAt.setDate(expiresAt.getDate() + KEY_TTL_DAYS);
|
|
@@ -8655,7 +8935,7 @@ function isExpired(expiresAt) {
|
|
|
8655
8935
|
return expiresAt !== null && /* @__PURE__ */ new Date() > expiresAt;
|
|
8656
8936
|
}
|
|
8657
8937
|
async function registerPublicKeyService(params) {
|
|
8658
|
-
const { userId, keyId, publicKey, fingerprint, algorithm =
|
|
8938
|
+
const { userId, keyId, publicKey, fingerprint, algorithm = DEFAULT_KEY_ALGORITHM, deviceName, platform } = params;
|
|
8659
8939
|
const existing = await keysRepository.findByKeyId(keyId);
|
|
8660
8940
|
if (existing) {
|
|
8661
8941
|
if (existing.userId === userId && existing.isActive) {
|
|
@@ -8683,7 +8963,7 @@ async function registerPublicKeyService(params) {
|
|
|
8683
8963
|
});
|
|
8684
8964
|
}
|
|
8685
8965
|
async function rotateKeyService(params) {
|
|
8686
|
-
const { userId, oldKeyId, newKeyId, newPublicKey, fingerprint, algorithm =
|
|
8966
|
+
const { userId, oldKeyId, newKeyId, newPublicKey, fingerprint, algorithm = DEFAULT_KEY_ALGORITHM } = params;
|
|
8687
8967
|
const isValidFingerprint = verifyKeyFingerprint(newPublicKey, fingerprint);
|
|
8688
8968
|
if (!isValidFingerprint) {
|
|
8689
8969
|
throw new InvalidKeyFingerprintError();
|
|
@@ -8734,6 +9014,7 @@ async function listKeysService(params) {
|
|
|
8734
9014
|
async function revokeAllKeysService(params) {
|
|
8735
9015
|
const { userId, currentKeyId, includeCurrent = false, reason } = params;
|
|
8736
9016
|
const revoked = includeCurrent ? await keysRepository.revokeAllActiveByUserId(userId, reason) : await keysRepository.revokeAllActiveByUserIdExcept(userId, currentKeyId, reason);
|
|
9017
|
+
await deviceAuthorizationsRepository.denyAllActiveByUserId(userId);
|
|
8737
9018
|
return { revokedCount: revoked.length, currentKeyRevoked: includeCurrent };
|
|
8738
9019
|
}
|
|
8739
9020
|
|
|
@@ -8859,11 +9140,17 @@ var AuthProviderSchema = Type.Union([
|
|
|
8859
9140
|
Type.Literal("phone"),
|
|
8860
9141
|
...SOCIAL_PROVIDERS.map((p) => Type.Literal(p))
|
|
8861
9142
|
]);
|
|
9143
|
+
var AuthLoginProviderSchema = Type.Union([
|
|
9144
|
+
Type.Literal("email"),
|
|
9145
|
+
Type.Literal("phone"),
|
|
9146
|
+
Type.Literal("device"),
|
|
9147
|
+
...SOCIAL_PROVIDERS.map((p) => Type.Literal(p))
|
|
9148
|
+
]);
|
|
8862
9149
|
var authLoginEvent = defineEvent(
|
|
8863
9150
|
"auth.login",
|
|
8864
9151
|
Type.Object({
|
|
8865
9152
|
userId: Type.String(),
|
|
8866
|
-
provider:
|
|
9153
|
+
provider: AuthLoginProviderSchema,
|
|
8867
9154
|
email: Type.Optional(Type.String()),
|
|
8868
9155
|
phone: Type.Optional(Type.String())
|
|
8869
9156
|
})
|
|
@@ -8971,8 +9258,8 @@ async function verifyReauthCredential(user, params) {
|
|
|
8971
9258
|
throw new VerificationTokenTargetMismatchError();
|
|
8972
9259
|
}
|
|
8973
9260
|
}
|
|
8974
|
-
async function sendDeletionEmail(to, subject,
|
|
8975
|
-
const result = await sendEmail2({ to, subject, text:
|
|
9261
|
+
async function sendDeletionEmail(to, subject, text15) {
|
|
9262
|
+
const result = await sendEmail2({ to, subject, text: text15 });
|
|
8976
9263
|
if (!result.success) {
|
|
8977
9264
|
authLogger.email.error("Failed to send account deletion email", { to, subject, error: result.error });
|
|
8978
9265
|
}
|
|
@@ -9024,12 +9311,12 @@ async function requestAccountDeletionService(userId, params) {
|
|
|
9024
9311
|
if (requestedBy === "self") {
|
|
9025
9312
|
await verifyReauthCredential(user, { password, verificationToken });
|
|
9026
9313
|
}
|
|
9027
|
-
const
|
|
9314
|
+
const config3 = getDeletionConfig();
|
|
9028
9315
|
const wantsImmediate = immediate === true;
|
|
9029
|
-
if (wantsImmediate && requestedBy === "self" && !
|
|
9316
|
+
if (wantsImmediate && requestedBy === "self" && !config3.allowSelfImmediate) {
|
|
9030
9317
|
throw new ImmediateDeletionNotAllowedError();
|
|
9031
9318
|
}
|
|
9032
|
-
const gracePeriodDays = wantsImmediate ? 0 :
|
|
9319
|
+
const gracePeriodDays = wantsImmediate ? 0 : config3.gracePeriodDays;
|
|
9033
9320
|
const requestedAt = /* @__PURE__ */ new Date();
|
|
9034
9321
|
const purgeScheduledAt = addDays(requestedAt, gracePeriodDays);
|
|
9035
9322
|
await usersRepository.updateById(user.id, { status: "pending_deletion" });
|
|
@@ -9051,6 +9338,7 @@ async function requestAccountDeletionService(userId, params) {
|
|
|
9051
9338
|
throw error;
|
|
9052
9339
|
}
|
|
9053
9340
|
await keysRepository.revokeAllActiveByUserId(user.id, "Account deletion requested");
|
|
9341
|
+
await deviceAuthorizationsRepository.denyAllActiveByUserId(user.id);
|
|
9054
9342
|
onAfterCommit(() => authDeletionRequestedEvent.emit({
|
|
9055
9343
|
userId: String(user.id),
|
|
9056
9344
|
userPublicId: user.publicId,
|
|
@@ -9133,10 +9421,10 @@ async function purgePendingRequest(request) {
|
|
|
9133
9421
|
if (!precheckUser || precheckUser.status !== "pending_deletion") {
|
|
9134
9422
|
return { outcome: "skipped" };
|
|
9135
9423
|
}
|
|
9136
|
-
const
|
|
9137
|
-
if (
|
|
9424
|
+
const config3 = getDeletionConfig();
|
|
9425
|
+
if (config3.onBeforePurge) {
|
|
9138
9426
|
try {
|
|
9139
|
-
await
|
|
9427
|
+
await config3.onBeforePurge({
|
|
9140
9428
|
id: precheckUser.id,
|
|
9141
9429
|
publicId: precheckUser.publicId,
|
|
9142
9430
|
email: precheckUser.email,
|
|
@@ -9150,7 +9438,7 @@ async function purgePendingRequest(request) {
|
|
|
9150
9438
|
return { outcome: "skipped" };
|
|
9151
9439
|
}
|
|
9152
9440
|
}
|
|
9153
|
-
const purgeStrategy =
|
|
9441
|
+
const purgeStrategy = config3.purgeStrategy;
|
|
9154
9442
|
let purgedUser = null;
|
|
9155
9443
|
await runInTransaction(async () => {
|
|
9156
9444
|
const user = await usersRepository.findById(userId);
|
|
@@ -9371,6 +9659,7 @@ async function changePasswordService(params) {
|
|
|
9371
9659
|
}
|
|
9372
9660
|
const newPasswordHash = await hashPassword(newPassword);
|
|
9373
9661
|
await usersRepository.updatePassword(userId, newPasswordHash, true);
|
|
9662
|
+
await deviceAuthorizationsRepository.denyAllActiveByUserId(userId);
|
|
9374
9663
|
await keysRepository.revokeAllActiveByUserId(userId, "Revoked by password change");
|
|
9375
9664
|
}
|
|
9376
9665
|
|
|
@@ -9526,10 +9815,238 @@ async function completeSignupService(params) {
|
|
|
9526
9815
|
});
|
|
9527
9816
|
}
|
|
9528
9817
|
|
|
9818
|
+
// src/server/services/device-auth.service.ts
|
|
9819
|
+
init_repositories();
|
|
9820
|
+
import {
|
|
9821
|
+
AccountDisabledError as AccountDisabledError2,
|
|
9822
|
+
AccountPendingDeletionError as AccountPendingDeletionError2,
|
|
9823
|
+
DeviceAuthNotFoundError,
|
|
9824
|
+
DeviceAuthExpiredError,
|
|
9825
|
+
DeviceAuthAlreadyHandledError,
|
|
9826
|
+
DeviceAuthDeniedError,
|
|
9827
|
+
InvalidKeyFingerprintError as InvalidKeyFingerprintError2
|
|
9828
|
+
} from "@spfn/auth/errors";
|
|
9829
|
+
import { onAfterCommit as onAfterCommit2 } from "@spfn/core/db";
|
|
9830
|
+
|
|
9831
|
+
// src/server/lib/device-auth-config.ts
|
|
9832
|
+
var DEFAULT_DEVICE_AUTH_TTL_MS = 10 * 60 * 1e3;
|
|
9833
|
+
var DEFAULT_DEVICE_AUTH_INTERVAL_MS = 5 * 1e3;
|
|
9834
|
+
var config2 = {
|
|
9835
|
+
ttlMs: DEFAULT_DEVICE_AUTH_TTL_MS,
|
|
9836
|
+
intervalMs: DEFAULT_DEVICE_AUTH_INTERVAL_MS
|
|
9837
|
+
};
|
|
9838
|
+
function configureDeviceAuth(options) {
|
|
9839
|
+
const ttlMs = options?.ttlMs ?? DEFAULT_DEVICE_AUTH_TTL_MS;
|
|
9840
|
+
const intervalMs = options?.intervalMs ?? DEFAULT_DEVICE_AUTH_INTERVAL_MS;
|
|
9841
|
+
assertWholeMillis("ttlMs", ttlMs);
|
|
9842
|
+
assertWholeMillis("intervalMs", intervalMs);
|
|
9843
|
+
config2 = { ttlMs, intervalMs };
|
|
9844
|
+
}
|
|
9845
|
+
function assertWholeMillis(name, value) {
|
|
9846
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
9847
|
+
throw new Error(
|
|
9848
|
+
`deviceAuth.${name} must be a positive whole number of milliseconds, received ${value}.`
|
|
9849
|
+
);
|
|
9850
|
+
}
|
|
9851
|
+
}
|
|
9852
|
+
function getDeviceAuthConfig() {
|
|
9853
|
+
return config2;
|
|
9854
|
+
}
|
|
9855
|
+
|
|
9856
|
+
// src/server/lib/device-code.ts
|
|
9857
|
+
import { createHash, randomBytes, randomInt } from "crypto";
|
|
9858
|
+
var USER_CODE_ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZ23456789";
|
|
9859
|
+
var USER_CODE_LENGTH = 8;
|
|
9860
|
+
var USER_CODE_GROUP_SIZE = 4;
|
|
9861
|
+
var DEVICE_CODE_BYTES = 32;
|
|
9862
|
+
function generateUserCode() {
|
|
9863
|
+
let code = "";
|
|
9864
|
+
for (let position = 0; position < USER_CODE_LENGTH; position++) {
|
|
9865
|
+
code += USER_CODE_ALPHABET[randomInt(USER_CODE_ALPHABET.length)];
|
|
9866
|
+
}
|
|
9867
|
+
return code;
|
|
9868
|
+
}
|
|
9869
|
+
function formatUserCode(userCode) {
|
|
9870
|
+
return `${userCode.slice(0, USER_CODE_GROUP_SIZE)}-${userCode.slice(USER_CODE_GROUP_SIZE)}`;
|
|
9871
|
+
}
|
|
9872
|
+
function normalizeUserCode(input) {
|
|
9873
|
+
return input.replace(/[^A-Za-z0-9]/g, "").toUpperCase();
|
|
9874
|
+
}
|
|
9875
|
+
function generateDeviceCode() {
|
|
9876
|
+
return randomBytes(DEVICE_CODE_BYTES).toString("base64url");
|
|
9877
|
+
}
|
|
9878
|
+
function hashDeviceCode(deviceCode) {
|
|
9879
|
+
return createHash("sha256").update(deviceCode).digest("hex");
|
|
9880
|
+
}
|
|
9881
|
+
|
|
9882
|
+
// src/server/services/device-auth.service.ts
|
|
9883
|
+
var USER_CODE_ATTEMPTS = 3;
|
|
9884
|
+
function assertActionable(record) {
|
|
9885
|
+
if (!record || record.status === "consumed") {
|
|
9886
|
+
throw new DeviceAuthNotFoundError();
|
|
9887
|
+
}
|
|
9888
|
+
if (record.expiresAt.getTime() < Date.now()) {
|
|
9889
|
+
throw new DeviceAuthExpiredError();
|
|
9890
|
+
}
|
|
9891
|
+
return record;
|
|
9892
|
+
}
|
|
9893
|
+
function refuseMissedTransition(record, from, moved) {
|
|
9894
|
+
if (!record || record.status === "consumed") {
|
|
9895
|
+
throw new DeviceAuthNotFoundError();
|
|
9896
|
+
}
|
|
9897
|
+
if (record.status === from) {
|
|
9898
|
+
throw new DeviceAuthExpiredError();
|
|
9899
|
+
}
|
|
9900
|
+
throw moved();
|
|
9901
|
+
}
|
|
9902
|
+
function describeDevice(record) {
|
|
9903
|
+
return {
|
|
9904
|
+
deviceName: record.deviceName ?? void 0,
|
|
9905
|
+
platform: record.platform ?? void 0,
|
|
9906
|
+
fingerprintPrefix: record.fingerprint.slice(0, KEY_FINGERPRINT_PREFIX_LENGTH),
|
|
9907
|
+
requestedAtMillis: record.createdAt.getTime(),
|
|
9908
|
+
expiresAtMillis: record.expiresAt.getTime()
|
|
9909
|
+
};
|
|
9910
|
+
}
|
|
9911
|
+
async function startDeviceAuthService(params) {
|
|
9912
|
+
if (!verifyKeyFingerprint(params.publicKey, params.fingerprint)) {
|
|
9913
|
+
throw new InvalidKeyFingerprintError2();
|
|
9914
|
+
}
|
|
9915
|
+
const { ttlMs, intervalMs } = getDeviceAuthConfig();
|
|
9916
|
+
const expiresAt = new Date(Date.now() + ttlMs);
|
|
9917
|
+
for (let attempt = 0; attempt < USER_CODE_ATTEMPTS; attempt++) {
|
|
9918
|
+
const deviceCode = generateDeviceCode();
|
|
9919
|
+
const userCode = generateUserCode();
|
|
9920
|
+
const record = await deviceAuthorizationsRepository.create({
|
|
9921
|
+
deviceCodeHash: hashDeviceCode(deviceCode),
|
|
9922
|
+
userCode,
|
|
9923
|
+
publicKey: params.publicKey,
|
|
9924
|
+
keyId: params.keyId,
|
|
9925
|
+
fingerprint: params.fingerprint,
|
|
9926
|
+
algorithm: params.algorithm,
|
|
9927
|
+
deviceName: params.deviceName,
|
|
9928
|
+
platform: params.platform,
|
|
9929
|
+
expiresAt
|
|
9930
|
+
});
|
|
9931
|
+
if (record) {
|
|
9932
|
+
return {
|
|
9933
|
+
deviceCode,
|
|
9934
|
+
userCode: formatUserCode(userCode),
|
|
9935
|
+
expiresAtMillis: expiresAt.getTime(),
|
|
9936
|
+
intervalMillis: intervalMs
|
|
9937
|
+
};
|
|
9938
|
+
}
|
|
9939
|
+
}
|
|
9940
|
+
throw new Error(
|
|
9941
|
+
`Could not allocate a unique device user code in ${USER_CODE_ATTEMPTS} attempts. Check the code generator and the user_code unique index.`
|
|
9942
|
+
);
|
|
9943
|
+
}
|
|
9944
|
+
async function getDeviceAuthInfoService(params) {
|
|
9945
|
+
const record = assertActionable(
|
|
9946
|
+
await deviceAuthorizationsRepository.findByUserCode(normalizeUserCode(params.userCode))
|
|
9947
|
+
);
|
|
9948
|
+
if (record.status !== "pending") {
|
|
9949
|
+
throw new DeviceAuthAlreadyHandledError();
|
|
9950
|
+
}
|
|
9951
|
+
return describeDevice(record);
|
|
9952
|
+
}
|
|
9953
|
+
async function approveDeviceAuthService(params) {
|
|
9954
|
+
const userCode = normalizeUserCode(params.userCode);
|
|
9955
|
+
const record = assertActionable(
|
|
9956
|
+
await deviceAuthorizationsRepository.findByUserCode(userCode)
|
|
9957
|
+
);
|
|
9958
|
+
const approved = await deviceAuthorizationsRepository.approve(record.id, params.userId);
|
|
9959
|
+
if (!approved) {
|
|
9960
|
+
refuseMissedTransition(
|
|
9961
|
+
await deviceAuthorizationsRepository.findByUserCode(userCode),
|
|
9962
|
+
"pending",
|
|
9963
|
+
() => new DeviceAuthAlreadyHandledError()
|
|
9964
|
+
);
|
|
9965
|
+
}
|
|
9966
|
+
return describeDevice(approved);
|
|
9967
|
+
}
|
|
9968
|
+
async function denyDeviceAuthService(params) {
|
|
9969
|
+
const userCode = normalizeUserCode(params.userCode);
|
|
9970
|
+
const record = assertActionable(
|
|
9971
|
+
await deviceAuthorizationsRepository.findByUserCode(userCode)
|
|
9972
|
+
);
|
|
9973
|
+
const denied = await deviceAuthorizationsRepository.deny(record.id);
|
|
9974
|
+
if (!denied) {
|
|
9975
|
+
refuseMissedTransition(
|
|
9976
|
+
await deviceAuthorizationsRepository.findByUserCode(userCode),
|
|
9977
|
+
"pending",
|
|
9978
|
+
() => new DeviceAuthAlreadyHandledError()
|
|
9979
|
+
);
|
|
9980
|
+
}
|
|
9981
|
+
}
|
|
9982
|
+
async function pollDeviceAuthService(params) {
|
|
9983
|
+
const deviceCodeHash = hashDeviceCode(params.deviceCode);
|
|
9984
|
+
const record = assertActionable(
|
|
9985
|
+
await deviceAuthorizationsRepository.findByDeviceCodeHash(deviceCodeHash)
|
|
9986
|
+
);
|
|
9987
|
+
if (record.status === "denied") {
|
|
9988
|
+
throw new DeviceAuthDeniedError();
|
|
9989
|
+
}
|
|
9990
|
+
if (record.status === "pending") {
|
|
9991
|
+
return { status: "pending", intervalMillis: getDeviceAuthConfig().intervalMs };
|
|
9992
|
+
}
|
|
9993
|
+
const consumed = await deviceAuthorizationsRepository.consumeApproved(deviceCodeHash);
|
|
9994
|
+
if (!consumed) {
|
|
9995
|
+
refuseMissedTransition(
|
|
9996
|
+
await deviceAuthorizationsRepository.findByDeviceCodeHash(deviceCodeHash),
|
|
9997
|
+
"approved",
|
|
9998
|
+
() => new DeviceAuthNotFoundError()
|
|
9999
|
+
);
|
|
10000
|
+
}
|
|
10001
|
+
return { status: "approved", ...await completeDeviceLogin(consumed) };
|
|
10002
|
+
}
|
|
10003
|
+
async function completeDeviceLogin(record) {
|
|
10004
|
+
if (record.userId === null) {
|
|
10005
|
+
throw new DeviceAuthNotFoundError();
|
|
10006
|
+
}
|
|
10007
|
+
const user = await usersRepository.findById(record.userId);
|
|
10008
|
+
if (!user) {
|
|
10009
|
+
throw new DeviceAuthNotFoundError();
|
|
10010
|
+
}
|
|
10011
|
+
if (user.status !== "active") {
|
|
10012
|
+
if (user.status === "pending_deletion") {
|
|
10013
|
+
const pending = await getPendingDeletionInfo(user.id);
|
|
10014
|
+
throw new AccountPendingDeletionError2({
|
|
10015
|
+
purgeScheduledAt: pending?.purgeScheduledAt.toISOString()
|
|
10016
|
+
});
|
|
10017
|
+
}
|
|
10018
|
+
throw new AccountDisabledError2({ status: user.status });
|
|
10019
|
+
}
|
|
10020
|
+
await registerPublicKeyService({
|
|
10021
|
+
userId: user.id,
|
|
10022
|
+
keyId: record.keyId,
|
|
10023
|
+
publicKey: record.publicKey,
|
|
10024
|
+
fingerprint: record.fingerprint,
|
|
10025
|
+
algorithm: record.algorithm,
|
|
10026
|
+
deviceName: record.deviceName ?? void 0,
|
|
10027
|
+
platform: record.platform ?? void 0
|
|
10028
|
+
});
|
|
10029
|
+
await updateLastLoginService(user.id);
|
|
10030
|
+
const result = {
|
|
10031
|
+
userId: String(user.id),
|
|
10032
|
+
publicId: user.publicId,
|
|
10033
|
+
email: user.email || void 0,
|
|
10034
|
+
phone: user.phone || void 0,
|
|
10035
|
+
passwordChangeRequired: user.passwordChangeRequired
|
|
10036
|
+
};
|
|
10037
|
+
onAfterCommit2(() => authLoginEvent.emit({
|
|
10038
|
+
userId: result.userId,
|
|
10039
|
+
provider: "device",
|
|
10040
|
+
email: result.email,
|
|
10041
|
+
phone: result.phone
|
|
10042
|
+
}));
|
|
10043
|
+
return result;
|
|
10044
|
+
}
|
|
10045
|
+
|
|
9529
10046
|
// src/server/services/rbac.service.ts
|
|
9530
10047
|
init_repositories();
|
|
9531
10048
|
init_rbac();
|
|
9532
|
-
import { createHash } from "crypto";
|
|
10049
|
+
import { createHash as createHash2 } from "crypto";
|
|
9533
10050
|
var RBAC_HASH_KEY = "rbac_config_hash";
|
|
9534
10051
|
function computeConfigHash(allRoles, allPermissions, allMappings) {
|
|
9535
10052
|
const payload = JSON.stringify({
|
|
@@ -9540,7 +10057,7 @@ function computeConfigHash(allRoles, allPermissions, allMappings) {
|
|
|
9540
10057
|
return acc;
|
|
9541
10058
|
}, {})
|
|
9542
10059
|
});
|
|
9543
|
-
return
|
|
10060
|
+
return createHash2("sha256").update(payload).digest("hex");
|
|
9544
10061
|
}
|
|
9545
10062
|
function collectMappings(options) {
|
|
9546
10063
|
const allMappings = { ...BUILTIN_ROLE_PERMISSIONS };
|
|
@@ -9598,51 +10115,51 @@ async function initializeAuth(options = {}) {
|
|
|
9598
10115
|
authLogger.service.info("\u{1F512} Built-in roles: user, admin, superadmin");
|
|
9599
10116
|
}
|
|
9600
10117
|
async function syncRoles(configs, existingByName) {
|
|
9601
|
-
for (const
|
|
9602
|
-
const existing = existingByName.get(
|
|
10118
|
+
for (const config3 of configs) {
|
|
10119
|
+
const existing = existingByName.get(config3.name);
|
|
9603
10120
|
if (!existing) {
|
|
9604
10121
|
await rolesRepository.create({
|
|
9605
|
-
name:
|
|
9606
|
-
displayName:
|
|
9607
|
-
description:
|
|
9608
|
-
priority:
|
|
9609
|
-
isSystem:
|
|
9610
|
-
isBuiltin:
|
|
10122
|
+
name: config3.name,
|
|
10123
|
+
displayName: config3.displayName,
|
|
10124
|
+
description: config3.description || null,
|
|
10125
|
+
priority: config3.priority ?? 10,
|
|
10126
|
+
isSystem: config3.isSystem ?? false,
|
|
10127
|
+
isBuiltin: config3.isBuiltin ?? false,
|
|
9611
10128
|
isActive: true
|
|
9612
10129
|
});
|
|
9613
|
-
authLogger.service.info(` \u2705 Created role: ${
|
|
10130
|
+
authLogger.service.info(` \u2705 Created role: ${config3.name}`);
|
|
9614
10131
|
} else {
|
|
9615
10132
|
const updateData = {
|
|
9616
|
-
displayName:
|
|
9617
|
-
description:
|
|
10133
|
+
displayName: config3.displayName,
|
|
10134
|
+
description: config3.description || null
|
|
9618
10135
|
};
|
|
9619
10136
|
if (!existing.isBuiltin) {
|
|
9620
|
-
updateData.priority =
|
|
10137
|
+
updateData.priority = config3.priority ?? existing.priority;
|
|
9621
10138
|
}
|
|
9622
10139
|
await rolesRepository.updateById(existing.id, updateData);
|
|
9623
10140
|
}
|
|
9624
10141
|
}
|
|
9625
10142
|
}
|
|
9626
10143
|
async function syncPermissions(configs, existingByName) {
|
|
9627
|
-
for (const
|
|
9628
|
-
const existing = existingByName.get(
|
|
10144
|
+
for (const config3 of configs) {
|
|
10145
|
+
const existing = existingByName.get(config3.name);
|
|
9629
10146
|
if (!existing) {
|
|
9630
10147
|
await permissionsRepository.create({
|
|
9631
|
-
name:
|
|
9632
|
-
displayName:
|
|
9633
|
-
description:
|
|
9634
|
-
category:
|
|
9635
|
-
isSystem:
|
|
9636
|
-
isBuiltin:
|
|
10148
|
+
name: config3.name,
|
|
10149
|
+
displayName: config3.displayName,
|
|
10150
|
+
description: config3.description || null,
|
|
10151
|
+
category: config3.category || null,
|
|
10152
|
+
isSystem: config3.isSystem ?? false,
|
|
10153
|
+
isBuiltin: config3.isBuiltin ?? false,
|
|
9637
10154
|
isActive: true,
|
|
9638
10155
|
metadata: null
|
|
9639
10156
|
});
|
|
9640
|
-
authLogger.service.info(` \u2705 Created permission: ${
|
|
10157
|
+
authLogger.service.info(` \u2705 Created permission: ${config3.name}`);
|
|
9641
10158
|
} else {
|
|
9642
10159
|
await permissionsRepository.updateById(existing.id, {
|
|
9643
|
-
displayName:
|
|
9644
|
-
description:
|
|
9645
|
-
category:
|
|
10160
|
+
displayName: config3.displayName,
|
|
10161
|
+
description: config3.description || null,
|
|
10162
|
+
category: config3.category || null
|
|
9646
10163
|
});
|
|
9647
10164
|
}
|
|
9648
10165
|
}
|
|
@@ -9703,7 +10220,7 @@ async function getUserPermissions(userId) {
|
|
|
9703
10220
|
const permIds = rolePermMappings.map((rp) => rp.permissionId);
|
|
9704
10221
|
if (permIds.length > 0) {
|
|
9705
10222
|
const rolePerms = await Promise.all(
|
|
9706
|
-
permIds.map((
|
|
10223
|
+
permIds.map((id15) => permissionsRepository.findById(id15))
|
|
9707
10224
|
);
|
|
9708
10225
|
for (const perm of rolePerms) {
|
|
9709
10226
|
if (perm && perm.isActive) {
|
|
@@ -9916,20 +10433,20 @@ async function acceptInvitation(params) {
|
|
|
9916
10433
|
async function listInvitations(params) {
|
|
9917
10434
|
return await invitationsRepository.list(params);
|
|
9918
10435
|
}
|
|
9919
|
-
async function cancelInvitation(
|
|
9920
|
-
const invitation = await invitationsRepository.findById(
|
|
10436
|
+
async function cancelInvitation(id15, cancelledBy, reason) {
|
|
10437
|
+
const invitation = await invitationsRepository.findById(id15);
|
|
9921
10438
|
if (!invitation) {
|
|
9922
10439
|
throw new NotFoundError3({ message: "Invitation not found", resource: "Invitation" });
|
|
9923
10440
|
}
|
|
9924
10441
|
if (invitation.status !== "pending") {
|
|
9925
10442
|
throw new ConflictError({ message: `Cannot cancel ${invitation.status} invitation` });
|
|
9926
10443
|
}
|
|
9927
|
-
await invitationsRepository.cancel(
|
|
10444
|
+
await invitationsRepository.cancel(id15, cancelledBy, reason, invitation.metadata);
|
|
9928
10445
|
console.log(`[Auth] \u26A0\uFE0F Invitation cancelled: ${invitation.email} (reason: ${reason || "none"})`);
|
|
9929
10446
|
}
|
|
9930
|
-
async function deleteInvitation(
|
|
9931
|
-
await invitationsRepository.deleteById(
|
|
9932
|
-
console.log(`[Auth] \u{1F5D1}\uFE0F Invitation deleted: ${
|
|
10447
|
+
async function deleteInvitation(id15) {
|
|
10448
|
+
await invitationsRepository.deleteById(id15);
|
|
10449
|
+
console.log(`[Auth] \u{1F5D1}\uFE0F Invitation deleted: ${id15}`);
|
|
9933
10450
|
}
|
|
9934
10451
|
async function expireOldInvitations() {
|
|
9935
10452
|
const count = await invitationsRepository.updateExpiredInvitations();
|
|
@@ -9938,8 +10455,8 @@ async function expireOldInvitations() {
|
|
|
9938
10455
|
}
|
|
9939
10456
|
return count;
|
|
9940
10457
|
}
|
|
9941
|
-
async function resendInvitation(
|
|
9942
|
-
const invitation = await invitationsRepository.findById(
|
|
10458
|
+
async function resendInvitation(id15, expiresInDays = 7) {
|
|
10459
|
+
const invitation = await invitationsRepository.findById(id15);
|
|
9943
10460
|
if (!invitation) {
|
|
9944
10461
|
throw new NotFoundError3({ message: "Invitation not found", resource: "Invitation" });
|
|
9945
10462
|
}
|
|
@@ -9947,7 +10464,7 @@ async function resendInvitation(id14, expiresInDays = 7) {
|
|
|
9947
10464
|
throw new ConflictError({ message: `Cannot resend ${invitation.status} invitation` });
|
|
9948
10465
|
}
|
|
9949
10466
|
const newExpiresAt = calculateExpiresAt(expiresInDays);
|
|
9950
|
-
const updated = await invitationsRepository.resend(
|
|
10467
|
+
const updated = await invitationsRepository.resend(id15, newExpiresAt);
|
|
9951
10468
|
if (!updated) {
|
|
9952
10469
|
throw new Error("Failed to update invitation");
|
|
9953
10470
|
}
|
|
@@ -9986,13 +10503,13 @@ async function getAuthSessionService(userId) {
|
|
|
9986
10503
|
// src/server/lib/one-time-token.ts
|
|
9987
10504
|
import { SSETokenManager } from "@spfn/core/event/sse";
|
|
9988
10505
|
var manager = null;
|
|
9989
|
-
function initOneTimeTokenManager(
|
|
10506
|
+
function initOneTimeTokenManager(config3) {
|
|
9990
10507
|
if (manager) {
|
|
9991
10508
|
manager.destroy();
|
|
9992
10509
|
}
|
|
9993
10510
|
manager = new SSETokenManager({
|
|
9994
|
-
ttl:
|
|
9995
|
-
store:
|
|
10511
|
+
ttl: config3?.ttl,
|
|
10512
|
+
store: config3?.store
|
|
9996
10513
|
});
|
|
9997
10514
|
}
|
|
9998
10515
|
function getOneTimeTokenManager() {
|
|
@@ -10104,8 +10621,8 @@ init_repositories();
|
|
|
10104
10621
|
import { env as env12 } from "@spfn/auth/config";
|
|
10105
10622
|
import { ValidationError as ValidationError8 } from "@spfn/core/errors";
|
|
10106
10623
|
import {
|
|
10107
|
-
AccountDisabledError as
|
|
10108
|
-
AccountPendingDeletionError as
|
|
10624
|
+
AccountDisabledError as AccountDisabledError3,
|
|
10625
|
+
AccountPendingDeletionError as AccountPendingDeletionError3,
|
|
10109
10626
|
UnverifiedEmailLinkError
|
|
10110
10627
|
} from "@spfn/auth/errors";
|
|
10111
10628
|
|
|
@@ -10140,10 +10657,10 @@ function getDefaultScopes() {
|
|
|
10140
10657
|
}
|
|
10141
10658
|
function getGoogleAuthUrl(state, scopes) {
|
|
10142
10659
|
const resolvedScopes = scopes ?? getDefaultScopes();
|
|
10143
|
-
const
|
|
10660
|
+
const config3 = getGoogleOAuthConfig();
|
|
10144
10661
|
const params = new URLSearchParams({
|
|
10145
|
-
client_id:
|
|
10146
|
-
redirect_uri:
|
|
10662
|
+
client_id: config3.clientId,
|
|
10663
|
+
redirect_uri: config3.redirectUri,
|
|
10147
10664
|
response_type: "code",
|
|
10148
10665
|
scope: resolvedScopes.join(" "),
|
|
10149
10666
|
state,
|
|
@@ -10155,16 +10672,16 @@ function getGoogleAuthUrl(state, scopes) {
|
|
|
10155
10672
|
return `${GOOGLE_AUTH_URL}?${params.toString()}`;
|
|
10156
10673
|
}
|
|
10157
10674
|
async function exchangeCodeForTokens(code) {
|
|
10158
|
-
const
|
|
10675
|
+
const config3 = getGoogleOAuthConfig();
|
|
10159
10676
|
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
10160
10677
|
method: "POST",
|
|
10161
10678
|
headers: {
|
|
10162
10679
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
10163
10680
|
},
|
|
10164
10681
|
body: new URLSearchParams({
|
|
10165
|
-
client_id:
|
|
10166
|
-
client_secret:
|
|
10167
|
-
redirect_uri:
|
|
10682
|
+
client_id: config3.clientId,
|
|
10683
|
+
client_secret: config3.clientSecret,
|
|
10684
|
+
redirect_uri: config3.redirectUri,
|
|
10168
10685
|
grant_type: "authorization_code",
|
|
10169
10686
|
code
|
|
10170
10687
|
})
|
|
@@ -10188,15 +10705,15 @@ async function getGoogleUserInfo(accessToken) {
|
|
|
10188
10705
|
return response.json();
|
|
10189
10706
|
}
|
|
10190
10707
|
async function refreshAccessToken(refreshToken) {
|
|
10191
|
-
const
|
|
10708
|
+
const config3 = getGoogleOAuthConfig();
|
|
10192
10709
|
const response = await fetch(GOOGLE_TOKEN_URL, {
|
|
10193
10710
|
method: "POST",
|
|
10194
10711
|
headers: {
|
|
10195
10712
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
10196
10713
|
},
|
|
10197
10714
|
body: new URLSearchParams({
|
|
10198
|
-
client_id:
|
|
10199
|
-
client_secret:
|
|
10715
|
+
client_id: config3.clientId,
|
|
10716
|
+
client_secret: config3.clientSecret,
|
|
10200
10717
|
refresh_token: refreshToken,
|
|
10201
10718
|
grant_type: "refresh_token"
|
|
10202
10719
|
})
|
|
@@ -10261,8 +10778,8 @@ var registry2 = /* @__PURE__ */ new Map();
|
|
|
10261
10778
|
function registerOAuthProvider(provider) {
|
|
10262
10779
|
registry2.set(provider.id, provider);
|
|
10263
10780
|
}
|
|
10264
|
-
function getOAuthProvider(
|
|
10265
|
-
return registry2.get(
|
|
10781
|
+
function getOAuthProvider(id15) {
|
|
10782
|
+
return registry2.get(id15);
|
|
10266
10783
|
}
|
|
10267
10784
|
function getRegisteredProviders() {
|
|
10268
10785
|
return [...registry2.values()];
|
|
@@ -10383,7 +10900,7 @@ var googleProvider = {
|
|
|
10383
10900
|
registerOAuthProvider(googleProvider);
|
|
10384
10901
|
|
|
10385
10902
|
// src/server/lib/oauth/apple-provider.ts
|
|
10386
|
-
import { createHash as
|
|
10903
|
+
import { createHash as createHash3 } from "crypto";
|
|
10387
10904
|
import { env as env11 } from "@spfn/auth/config";
|
|
10388
10905
|
import { ValidationError as ValidationError4 } from "@spfn/core/errors";
|
|
10389
10906
|
import { NativeSignInUnsupportedError as NativeSignInUnsupportedError2 } from "@spfn/auth/errors";
|
|
@@ -10393,7 +10910,7 @@ function getAppleClientIds() {
|
|
|
10393
10910
|
return (env11.SPFN_AUTH_APPLE_CLIENT_IDS || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
10394
10911
|
}
|
|
10395
10912
|
function hashNonce(rawNonce) {
|
|
10396
|
-
return
|
|
10913
|
+
return createHash3("sha256").update(rawNonce).digest("hex");
|
|
10397
10914
|
}
|
|
10398
10915
|
function unsupportedWebFlow() {
|
|
10399
10916
|
throw new ValidationError4({
|
|
@@ -10516,21 +11033,21 @@ var githubProvider = {
|
|
|
10516
11033
|
return !!(env3.SPFN_AUTH_GITHUB_CLIENT_ID && env3.SPFN_AUTH_GITHUB_CLIENT_SECRET);
|
|
10517
11034
|
},
|
|
10518
11035
|
getAuthUrl(state, scopes) {
|
|
10519
|
-
const
|
|
11036
|
+
const config3 = getGithubConfig();
|
|
10520
11037
|
const params = new URLSearchParams({
|
|
10521
|
-
client_id:
|
|
10522
|
-
redirect_uri:
|
|
11038
|
+
client_id: config3.clientId,
|
|
11039
|
+
redirect_uri: config3.redirectUri,
|
|
10523
11040
|
state,
|
|
10524
11041
|
scope: (scopes ?? getGithubScopes()).join(" ")
|
|
10525
11042
|
});
|
|
10526
11043
|
return `${GITHUB_AUTH_URL}?${params.toString()}`;
|
|
10527
11044
|
},
|
|
10528
11045
|
async exchangeCodeForTokens(code) {
|
|
10529
|
-
const
|
|
11046
|
+
const config3 = getGithubConfig();
|
|
10530
11047
|
return requestGithubTokens(new URLSearchParams({
|
|
10531
|
-
client_id:
|
|
10532
|
-
client_secret:
|
|
10533
|
-
redirect_uri:
|
|
11048
|
+
client_id: config3.clientId,
|
|
11049
|
+
client_secret: config3.clientSecret,
|
|
11050
|
+
redirect_uri: config3.redirectUri,
|
|
10534
11051
|
code
|
|
10535
11052
|
}));
|
|
10536
11053
|
},
|
|
@@ -10560,11 +11077,11 @@ var githubProvider = {
|
|
|
10560
11077
|
};
|
|
10561
11078
|
},
|
|
10562
11079
|
async refreshTokens(refreshToken) {
|
|
10563
|
-
const
|
|
11080
|
+
const config3 = getGithubConfig();
|
|
10564
11081
|
return requestGithubTokens(new URLSearchParams({
|
|
10565
11082
|
grant_type: "refresh_token",
|
|
10566
|
-
client_id:
|
|
10567
|
-
client_secret:
|
|
11083
|
+
client_id: config3.clientId,
|
|
11084
|
+
client_secret: config3.clientSecret,
|
|
10568
11085
|
refresh_token: refreshToken
|
|
10569
11086
|
}));
|
|
10570
11087
|
}
|
|
@@ -10687,26 +11204,26 @@ var kakaoProvider = {
|
|
|
10687
11204
|
return !!env3.SPFN_AUTH_KAKAO_CLIENT_ID;
|
|
10688
11205
|
},
|
|
10689
11206
|
getAuthUrl(state, scopes) {
|
|
10690
|
-
const
|
|
11207
|
+
const config3 = getKakaoConfig();
|
|
10691
11208
|
const params = new URLSearchParams({
|
|
10692
11209
|
response_type: "code",
|
|
10693
|
-
client_id:
|
|
10694
|
-
redirect_uri:
|
|
11210
|
+
client_id: config3.clientId,
|
|
11211
|
+
redirect_uri: config3.redirectUri,
|
|
10695
11212
|
state,
|
|
10696
11213
|
scope: (scopes ?? getKakaoScopes()).join(",")
|
|
10697
11214
|
});
|
|
10698
11215
|
return `${KAKAO_AUTH_URL}?${params.toString()}`;
|
|
10699
11216
|
},
|
|
10700
11217
|
async exchangeCodeForTokens(code) {
|
|
10701
|
-
const
|
|
11218
|
+
const config3 = getKakaoConfig();
|
|
10702
11219
|
const params = new URLSearchParams({
|
|
10703
11220
|
grant_type: "authorization_code",
|
|
10704
|
-
client_id:
|
|
10705
|
-
redirect_uri:
|
|
11221
|
+
client_id: config3.clientId,
|
|
11222
|
+
redirect_uri: config3.redirectUri,
|
|
10706
11223
|
code
|
|
10707
11224
|
});
|
|
10708
|
-
if (
|
|
10709
|
-
params.set("client_secret",
|
|
11225
|
+
if (config3.clientSecret) {
|
|
11226
|
+
params.set("client_secret", config3.clientSecret);
|
|
10710
11227
|
}
|
|
10711
11228
|
return requestKakaoTokens(params);
|
|
10712
11229
|
},
|
|
@@ -10748,14 +11265,14 @@ var kakaoProvider = {
|
|
|
10748
11265
|
return options.accessToken ? withKakaoVerifiedEmail(identity, options.accessToken) : identity;
|
|
10749
11266
|
},
|
|
10750
11267
|
async refreshTokens(refreshToken) {
|
|
10751
|
-
const
|
|
11268
|
+
const config3 = getKakaoConfig();
|
|
10752
11269
|
const params = new URLSearchParams({
|
|
10753
11270
|
grant_type: "refresh_token",
|
|
10754
|
-
client_id:
|
|
11271
|
+
client_id: config3.clientId,
|
|
10755
11272
|
refresh_token: refreshToken
|
|
10756
11273
|
});
|
|
10757
|
-
if (
|
|
10758
|
-
params.set("client_secret",
|
|
11274
|
+
if (config3.clientSecret) {
|
|
11275
|
+
params.set("client_secret", config3.clientSecret);
|
|
10759
11276
|
}
|
|
10760
11277
|
return requestKakaoTokens(params);
|
|
10761
11278
|
},
|
|
@@ -10792,7 +11309,7 @@ registerOAuthProvider(kakaoProvider);
|
|
|
10792
11309
|
init_config();
|
|
10793
11310
|
import { ValidationError as ValidationError7 } from "@spfn/core/errors";
|
|
10794
11311
|
import { NativeSignInUnsupportedError as NativeSignInUnsupportedError4 } from "@spfn/auth/errors";
|
|
10795
|
-
import { createDecipheriv, createHash as
|
|
11312
|
+
import { createDecipheriv, createHash as createHash4, createHmac, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
10796
11313
|
var NAVER_AUTH_URL = "https://nid.naver.com/oauth2.0/authorize";
|
|
10797
11314
|
var NAVER_TOKEN_URL = "https://nid.naver.com/oauth2.0/token";
|
|
10798
11315
|
var NAVER_USERINFO_URL = "https://openapi.naver.com/v1/nid/me";
|
|
@@ -10842,7 +11359,7 @@ async function requestNaverTokens(params) {
|
|
|
10842
11359
|
};
|
|
10843
11360
|
}
|
|
10844
11361
|
function deriveNaverUnlinkKey(clientSecret) {
|
|
10845
|
-
return
|
|
11362
|
+
return createHash4("md5").update(clientSecret).digest().subarray(0, 16);
|
|
10846
11363
|
}
|
|
10847
11364
|
function decodeBase64Url(value) {
|
|
10848
11365
|
return Buffer.from(value.replace(/-/g, "+").replace(/_/g, "/"), "base64");
|
|
@@ -10913,22 +11430,22 @@ var naverProvider = {
|
|
|
10913
11430
|
return !!(env3.SPFN_AUTH_NAVER_CLIENT_ID && env3.SPFN_AUTH_NAVER_CLIENT_SECRET);
|
|
10914
11431
|
},
|
|
10915
11432
|
getAuthUrl(state) {
|
|
10916
|
-
const
|
|
11433
|
+
const config3 = getNaverConfig();
|
|
10917
11434
|
const params = new URLSearchParams({
|
|
10918
11435
|
response_type: "code",
|
|
10919
|
-
client_id:
|
|
10920
|
-
redirect_uri:
|
|
11436
|
+
client_id: config3.clientId,
|
|
11437
|
+
redirect_uri: config3.redirectUri,
|
|
10921
11438
|
state
|
|
10922
11439
|
});
|
|
10923
11440
|
return `${NAVER_AUTH_URL}?${params.toString()}`;
|
|
10924
11441
|
},
|
|
10925
11442
|
async exchangeCodeForTokens(code, options) {
|
|
10926
|
-
const
|
|
11443
|
+
const config3 = getNaverConfig();
|
|
10927
11444
|
return requestNaverTokens(new URLSearchParams({
|
|
10928
11445
|
grant_type: "authorization_code",
|
|
10929
|
-
client_id:
|
|
10930
|
-
client_secret:
|
|
10931
|
-
redirect_uri:
|
|
11446
|
+
client_id: config3.clientId,
|
|
11447
|
+
client_secret: config3.clientSecret,
|
|
11448
|
+
redirect_uri: config3.redirectUri,
|
|
10932
11449
|
code,
|
|
10933
11450
|
state: options.state
|
|
10934
11451
|
}));
|
|
@@ -10969,11 +11486,11 @@ var naverProvider = {
|
|
|
10969
11486
|
return options.accessToken ? withNaverProfile(identity, options.accessToken) : identity;
|
|
10970
11487
|
},
|
|
10971
11488
|
async refreshTokens(refreshToken) {
|
|
10972
|
-
const
|
|
11489
|
+
const config3 = getNaverConfig();
|
|
10973
11490
|
return requestNaverTokens(new URLSearchParams({
|
|
10974
11491
|
grant_type: "refresh_token",
|
|
10975
|
-
client_id:
|
|
10976
|
-
client_secret:
|
|
11492
|
+
client_id: config3.clientId,
|
|
11493
|
+
client_secret: config3.clientSecret,
|
|
10977
11494
|
refresh_token: refreshToken
|
|
10978
11495
|
}));
|
|
10979
11496
|
},
|
|
@@ -11142,9 +11659,9 @@ async function assertActiveForOAuthSession(userId) {
|
|
|
11142
11659
|
}
|
|
11143
11660
|
if (user.status === "pending_deletion") {
|
|
11144
11661
|
const pending = await getPendingDeletionInfo(user.id);
|
|
11145
|
-
throw new
|
|
11662
|
+
throw new AccountPendingDeletionError3({ purgeScheduledAt: pending?.purgeScheduledAt.toISOString() });
|
|
11146
11663
|
}
|
|
11147
|
-
throw new
|
|
11664
|
+
throw new AccountDisabledError3({ status: user.status });
|
|
11148
11665
|
}
|
|
11149
11666
|
async function backfillVerifiedEmail(userId, identity) {
|
|
11150
11667
|
if (!identity.email || !identity.emailVerified) {
|
|
@@ -11287,9 +11804,9 @@ async function oauthUnlinkNotifyService(provider, notification) {
|
|
|
11287
11804
|
}
|
|
11288
11805
|
|
|
11289
11806
|
// src/server/services/oauth-native.service.ts
|
|
11290
|
-
import { runInTransaction as runInTransaction2, onAfterCommit as
|
|
11807
|
+
import { runInTransaction as runInTransaction2, onAfterCommit as onAfterCommit3 } from "@spfn/core/db";
|
|
11291
11808
|
import {
|
|
11292
|
-
InvalidKeyFingerprintError as
|
|
11809
|
+
InvalidKeyFingerprintError as InvalidKeyFingerprintError3,
|
|
11293
11810
|
NativeSignInUnsupportedError as NativeSignInUnsupportedError5,
|
|
11294
11811
|
NonceKeyBindingError
|
|
11295
11812
|
} from "@spfn/auth/errors";
|
|
@@ -11316,7 +11833,7 @@ function assertNonceBindsPublicKey(params) {
|
|
|
11316
11833
|
throw new NonceKeyBindingError();
|
|
11317
11834
|
}
|
|
11318
11835
|
if (!verifyKeyFingerprint(params.publicKey, params.fingerprint)) {
|
|
11319
|
-
throw new
|
|
11836
|
+
throw new InvalidKeyFingerprintError3();
|
|
11320
11837
|
}
|
|
11321
11838
|
}
|
|
11322
11839
|
async function persistNativeLogin(identity, params) {
|
|
@@ -11352,23 +11869,23 @@ async function persistNativeLogin(identity, params) {
|
|
|
11352
11869
|
email: identity.email || void 0,
|
|
11353
11870
|
metadata: params.metadata
|
|
11354
11871
|
};
|
|
11355
|
-
|
|
11872
|
+
onAfterCommit3(() => (isNewUser ? authRegisterEvent : authLoginEvent).emit(eventPayload));
|
|
11356
11873
|
return { userId: String(userId), keyId: params.keyId, isNewUser };
|
|
11357
11874
|
}, { context: "auth:oauth-native" });
|
|
11358
11875
|
}
|
|
11359
11876
|
|
|
11360
11877
|
// src/server/services/ops-token.service.ts
|
|
11361
11878
|
init_ops_tokens_repository();
|
|
11362
|
-
import { createHash as
|
|
11879
|
+
import { createHash as createHash5, randomBytes as randomBytes2 } from "crypto";
|
|
11363
11880
|
var OPS_TOKEN_PREFIX = "spfn_ops_";
|
|
11364
11881
|
function hashOpsToken(token) {
|
|
11365
|
-
return
|
|
11882
|
+
return createHash5("sha256").update(token).digest("hex");
|
|
11366
11883
|
}
|
|
11367
11884
|
async function issueOpsTokenService(name, scopes, expiresAt) {
|
|
11368
11885
|
if (scopes.length === 0) {
|
|
11369
11886
|
throw new Error("An ops token needs at least one scope ('*' grants all).");
|
|
11370
11887
|
}
|
|
11371
|
-
const token = OPS_TOKEN_PREFIX +
|
|
11888
|
+
const token = OPS_TOKEN_PREFIX + randomBytes2(32).toString("hex");
|
|
11372
11889
|
const record = await opsTokensRepository.create({
|
|
11373
11890
|
name,
|
|
11374
11891
|
tokenHash: hashOpsToken(token),
|
|
@@ -11398,8 +11915,8 @@ async function verifyOpsTokenService(token) {
|
|
|
11398
11915
|
scopes: record.scopes
|
|
11399
11916
|
};
|
|
11400
11917
|
}
|
|
11401
|
-
async function revokeOpsTokenService(
|
|
11402
|
-
return await opsTokensRepository.revokeById(
|
|
11918
|
+
async function revokeOpsTokenService(id15) {
|
|
11919
|
+
return await opsTokensRepository.revokeById(id15);
|
|
11403
11920
|
}
|
|
11404
11921
|
async function listOpsTokensService() {
|
|
11405
11922
|
return await opsTokensRepository.list();
|
|
@@ -11412,7 +11929,7 @@ import { rateLimitPolicy } from "@spfn/core/middleware";
|
|
|
11412
11929
|
|
|
11413
11930
|
// src/server/lib/rate-limit-keys.ts
|
|
11414
11931
|
init_email();
|
|
11415
|
-
import { createHash as
|
|
11932
|
+
import { createHash as createHash6 } from "crypto";
|
|
11416
11933
|
import { getClientIp } from "@spfn/core/middleware";
|
|
11417
11934
|
async function readJsonBody(c) {
|
|
11418
11935
|
try {
|
|
@@ -11448,11 +11965,20 @@ function byIpAndAccount(options = {}) {
|
|
|
11448
11965
|
];
|
|
11449
11966
|
};
|
|
11450
11967
|
}
|
|
11968
|
+
function byIpAndCaller(options = {}) {
|
|
11969
|
+
return async (c) => {
|
|
11970
|
+
const auth = getOptionalAuth(c);
|
|
11971
|
+
return [
|
|
11972
|
+
{ key: `ip:${getClientIp(c)}`, limit: options.ipLimit },
|
|
11973
|
+
auth ? `caller:${auth.userId}` : void 0
|
|
11974
|
+
];
|
|
11975
|
+
};
|
|
11976
|
+
}
|
|
11451
11977
|
function idTokenKey(body) {
|
|
11452
11978
|
if (typeof body.idToken !== "string" || !body.idToken) {
|
|
11453
11979
|
return void 0;
|
|
11454
11980
|
}
|
|
11455
|
-
return `tok:${
|
|
11981
|
+
return `tok:${createHash6("sha256").update(body.idToken).digest("hex")}`;
|
|
11456
11982
|
}
|
|
11457
11983
|
function byIpAndIdToken(options = {}) {
|
|
11458
11984
|
return async (c) => {
|
|
@@ -11610,6 +12136,69 @@ var login = route.post("/_auth/login").input({
|
|
|
11610
12136
|
const { body } = await c.data();
|
|
11611
12137
|
return await loginService(body);
|
|
11612
12138
|
});
|
|
12139
|
+
var startDeviceAuth = route.post("/_auth/device/start").input({
|
|
12140
|
+
// Bounded, unlike the interceptor bodies the authenticated enrolment
|
|
12141
|
+
// routes use: this is the one place key material arrives from a caller
|
|
12142
|
+
// with nothing to authenticate and is stored before anyone has agreed to
|
|
12143
|
+
// it. Validation refuses an oversize payload before it reaches a row.
|
|
12144
|
+
body: Type.Object({
|
|
12145
|
+
publicKey: PublicKeySchema,
|
|
12146
|
+
keyId: KeyIdSchema,
|
|
12147
|
+
fingerprint: FingerprintSchema,
|
|
12148
|
+
algorithm: Type.Optional(Type.Union(
|
|
12149
|
+
KEY_ALGORITHM.map((algo) => Type.Literal(algo)),
|
|
12150
|
+
{ description: "Signature algorithm" }
|
|
12151
|
+
)),
|
|
12152
|
+
deviceName: Type.Optional(DeviceNameSchema),
|
|
12153
|
+
platform: Type.Optional(PlatformSchema)
|
|
12154
|
+
})
|
|
12155
|
+
}).use([rateLimitPolicy("auth-device-start", { limit: 10, windowMs: 6e4 }), Transactional()]).skip(["auth"]).handler(async (c) => {
|
|
12156
|
+
const { body } = await c.data();
|
|
12157
|
+
return await startDeviceAuthService(body);
|
|
12158
|
+
});
|
|
12159
|
+
var pollDeviceAuth = route.post("/_auth/device/poll").input({
|
|
12160
|
+
body: Type.Object({
|
|
12161
|
+
deviceCode: Type.String({ description: "Device code returned by /_auth/device/start" })
|
|
12162
|
+
})
|
|
12163
|
+
}).use([rateLimitPolicy("auth-device-poll", { limit: 30, windowMs: 6e4 }), Transactional()]).skip(["auth"]).handler(async (c) => {
|
|
12164
|
+
const { body } = await c.data();
|
|
12165
|
+
return await pollDeviceAuthService(body);
|
|
12166
|
+
});
|
|
12167
|
+
var getDeviceAuthInfo = route.post("/_auth/device/info").input({
|
|
12168
|
+
body: Type.Object({
|
|
12169
|
+
userCode: UserCodeSchema
|
|
12170
|
+
})
|
|
12171
|
+
}).use([
|
|
12172
|
+
rateLimitPolicy("auth-device-info", { limit: 10, windowMs: 6e4, by: byIpAndCaller({ ipLimit: 50 }) }),
|
|
12173
|
+
Transactional()
|
|
12174
|
+
]).handler(async (c) => {
|
|
12175
|
+
const { body } = await c.data();
|
|
12176
|
+
return await getDeviceAuthInfoService(body);
|
|
12177
|
+
});
|
|
12178
|
+
var approveDeviceAuth = route.post("/_auth/device/approve").input({
|
|
12179
|
+
body: Type.Object({
|
|
12180
|
+
userCode: UserCodeSchema
|
|
12181
|
+
})
|
|
12182
|
+
}).use([
|
|
12183
|
+
rateLimitPolicy("auth-device-approve", { limit: 10, windowMs: 6e4, by: byIpAndCaller({ ipLimit: 50 }) }),
|
|
12184
|
+
Transactional()
|
|
12185
|
+
]).handler(async (c) => {
|
|
12186
|
+
const { body } = await c.data();
|
|
12187
|
+
const { userId } = getAuth(c);
|
|
12188
|
+
return await approveDeviceAuthService({ userCode: body.userCode, userId: Number(userId) });
|
|
12189
|
+
});
|
|
12190
|
+
var denyDeviceAuth = route.post("/_auth/device/deny").input({
|
|
12191
|
+
body: Type.Object({
|
|
12192
|
+
userCode: UserCodeSchema
|
|
12193
|
+
})
|
|
12194
|
+
}).use([
|
|
12195
|
+
rateLimitPolicy("auth-device-deny", { limit: 10, windowMs: 6e4, by: byIpAndCaller({ ipLimit: 50 }) }),
|
|
12196
|
+
Transactional()
|
|
12197
|
+
]).handler(async (c) => {
|
|
12198
|
+
const { body } = await c.data();
|
|
12199
|
+
await denyDeviceAuthService(body);
|
|
12200
|
+
return c.noContent();
|
|
12201
|
+
});
|
|
11613
12202
|
var logout = route.post("/_auth/logout").handler(async (c) => {
|
|
11614
12203
|
const auth = getAuth(c);
|
|
11615
12204
|
if (!auth) {
|
|
@@ -11718,6 +12307,11 @@ var authRouter = defineRouter({
|
|
|
11718
12307
|
verifyCode,
|
|
11719
12308
|
register,
|
|
11720
12309
|
login,
|
|
12310
|
+
startDeviceAuth,
|
|
12311
|
+
pollDeviceAuth,
|
|
12312
|
+
getDeviceAuthInfo,
|
|
12313
|
+
approveDeviceAuth,
|
|
12314
|
+
denyDeviceAuth,
|
|
11721
12315
|
logout,
|
|
11722
12316
|
rotateKey,
|
|
11723
12317
|
listKeys,
|
|
@@ -11732,8 +12326,8 @@ var authRouter = defineRouter({
|
|
|
11732
12326
|
import { EMAIL_PATTERN as EMAIL_PATTERN2, UUID_PATTERN } from "@spfn/auth";
|
|
11733
12327
|
|
|
11734
12328
|
// src/server/middleware/authenticate.ts
|
|
11735
|
-
import { defineMiddleware } from "@spfn/core/route";
|
|
11736
|
-
import { UnauthorizedError as
|
|
12329
|
+
import { defineMiddleware as defineMiddleware2 } from "@spfn/core/route";
|
|
12330
|
+
import { UnauthorizedError as UnauthorizedError3 } from "@spfn/core/errors";
|
|
11737
12331
|
import { verifyClientToken as verifyClientToken2, decodeToken as decodeToken2, authLogger as authLogger3, keysRepository as keysRepository3, usersRepository as usersRepository3, userProfilesRepository as userProfilesRepository3 } from "@spfn/auth/server";
|
|
11738
12332
|
import {
|
|
11739
12333
|
InvalidTokenError,
|
|
@@ -11742,7 +12336,7 @@ import {
|
|
|
11742
12336
|
} from "@spfn/auth/errors";
|
|
11743
12337
|
|
|
11744
12338
|
// src/server/client-proof/refusal.ts
|
|
11745
|
-
import { randomBytes as
|
|
12339
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
11746
12340
|
|
|
11747
12341
|
// src/server/client-proof/canonical-json.ts
|
|
11748
12342
|
var CanonicalJsonError = class extends Error {
|
|
@@ -11755,13 +12349,13 @@ var CanonicalJsonError = class extends Error {
|
|
|
11755
12349
|
var INT64_MIN = -(2n ** 63n);
|
|
11756
12350
|
var INT64_MAX = 2n ** 63n - 1n;
|
|
11757
12351
|
function parseCanonicalJson(bytes) {
|
|
11758
|
-
let
|
|
12352
|
+
let text15;
|
|
11759
12353
|
try {
|
|
11760
|
-
|
|
12354
|
+
text15 = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
11761
12355
|
} catch {
|
|
11762
12356
|
throw new CanonicalJsonError("INVALID_UTF8");
|
|
11763
12357
|
}
|
|
11764
|
-
const parser = new Parser(
|
|
12358
|
+
const parser = new Parser(text15);
|
|
11765
12359
|
const value = parser.parseValue();
|
|
11766
12360
|
parser.skipWhitespace();
|
|
11767
12361
|
if (!parser.atEnd()) {
|
|
@@ -11782,8 +12376,8 @@ function isCanonicalBytes(bytes, value) {
|
|
|
11782
12376
|
return true;
|
|
11783
12377
|
}
|
|
11784
12378
|
var Parser = class {
|
|
11785
|
-
constructor(
|
|
11786
|
-
this.text =
|
|
12379
|
+
constructor(text15) {
|
|
12380
|
+
this.text = text15;
|
|
11787
12381
|
}
|
|
11788
12382
|
pos = 0;
|
|
11789
12383
|
atEnd() {
|
|
@@ -12100,7 +12694,7 @@ var HTTP_STATUS = {
|
|
|
12100
12694
|
CONTRACT_UNSUPPORTED: 409
|
|
12101
12695
|
};
|
|
12102
12696
|
function newHexId() {
|
|
12103
|
-
return
|
|
12697
|
+
return randomBytes3(16).toString("hex");
|
|
12104
12698
|
}
|
|
12105
12699
|
var ClientProofRefusal = class _ClientProofRefusal {
|
|
12106
12700
|
constructor(code, message) {
|
|
@@ -12202,7 +12796,7 @@ function contractViolation(message) {
|
|
|
12202
12796
|
}
|
|
12203
12797
|
|
|
12204
12798
|
// src/server/client-proof/contract-bundle.ts
|
|
12205
|
-
import { createHash as
|
|
12799
|
+
import { createHash as createHash8 } from "crypto";
|
|
12206
12800
|
import {
|
|
12207
12801
|
CORE_TIME_OPERATION_ID as CORE_TIME_OPERATION_ID2,
|
|
12208
12802
|
ServerTimeResponseSchema
|
|
@@ -12210,7 +12804,7 @@ import {
|
|
|
12210
12804
|
init_types();
|
|
12211
12805
|
|
|
12212
12806
|
// src/server/client-proof/proof.ts
|
|
12213
|
-
import { createHash as
|
|
12807
|
+
import { createHash as createHash7, createPrivateKey, createPublicKey, sign, verify as verify2 } from "crypto";
|
|
12214
12808
|
var CLIENT_PROOF_PROFILE = "clientProofV1";
|
|
12215
12809
|
var ABSENT_BODY_SHA256 = "0".repeat(64);
|
|
12216
12810
|
var DEFAULT_REPLAY_WINDOW_MILLIS = 3e5;
|
|
@@ -12280,7 +12874,7 @@ function verifyClientProof(input, presentedProof, publicKey) {
|
|
|
12280
12874
|
);
|
|
12281
12875
|
}
|
|
12282
12876
|
function sha256Hex(bytes) {
|
|
12283
|
-
return
|
|
12877
|
+
return createHash7("sha256").update(bytes).digest("hex");
|
|
12284
12878
|
}
|
|
12285
12879
|
|
|
12286
12880
|
// src/server/client-proof/admission.ts
|
|
@@ -12372,8 +12966,8 @@ var CORE_PREREQUISITE_OPERATIONS = [
|
|
|
12372
12966
|
|
|
12373
12967
|
// src/server/client-proof/contract-bundle.ts
|
|
12374
12968
|
init_wire_headers();
|
|
12375
|
-
var CONTRACT_VERSION = "0.
|
|
12376
|
-
var CONTRACT_SUPPORTED_RANGE = ">=0.
|
|
12969
|
+
var CONTRACT_VERSION = "0.10.0";
|
|
12970
|
+
var CONTRACT_SUPPORTED_RANGE = ">=0.10.0 <0.11.0";
|
|
12377
12971
|
function required(name, type) {
|
|
12378
12972
|
return { name, type, optional: false };
|
|
12379
12973
|
}
|
|
@@ -12544,7 +13138,7 @@ var CONTRACT_TYPES = [
|
|
|
12544
13138
|
fields: [
|
|
12545
13139
|
required("keyId", "string"),
|
|
12546
13140
|
optional("deviceName", "string"),
|
|
12547
|
-
optional("platform", "
|
|
13141
|
+
optional("platform", "KeyPlatform"),
|
|
12548
13142
|
required("algorithm", "KeyAlgorithm"),
|
|
12549
13143
|
required("fingerprintPrefix", "string"),
|
|
12550
13144
|
required("createdAtMillis", "integer"),
|
|
@@ -12586,10 +13180,94 @@ var CONTRACT_TYPES = [
|
|
|
12586
13180
|
required("revokedCount", "integer"),
|
|
12587
13181
|
required("currentKeyRevoked", "boolean")
|
|
12588
13182
|
]
|
|
13183
|
+
},
|
|
13184
|
+
{
|
|
13185
|
+
name: "StartDeviceAuthRequest",
|
|
13186
|
+
fields: [
|
|
13187
|
+
required("publicKey", "string"),
|
|
13188
|
+
required("keyId", "string"),
|
|
13189
|
+
required("fingerprint", "string"),
|
|
13190
|
+
optional("algorithm", "KeyAlgorithm"),
|
|
13191
|
+
optional("deviceName", "string"),
|
|
13192
|
+
optional("platform", "KeyPlatform")
|
|
13193
|
+
]
|
|
13194
|
+
},
|
|
13195
|
+
{
|
|
13196
|
+
name: "StartDeviceAuthResponse",
|
|
13197
|
+
fields: [
|
|
13198
|
+
required("deviceCode", "string"),
|
|
13199
|
+
required("userCode", "string"),
|
|
13200
|
+
required("expiresAtMillis", "integer"),
|
|
13201
|
+
required("intervalMillis", "integer")
|
|
13202
|
+
]
|
|
13203
|
+
},
|
|
13204
|
+
{
|
|
13205
|
+
name: "PollDeviceAuthRequest",
|
|
13206
|
+
fields: [
|
|
13207
|
+
required("deviceCode", "string")
|
|
13208
|
+
]
|
|
13209
|
+
},
|
|
13210
|
+
/**
|
|
13211
|
+
* The poll union, flattened into the one shape this grammar can carry.
|
|
13212
|
+
*
|
|
13213
|
+
* `status` is the discriminant and the only required field; everything else
|
|
13214
|
+
* belongs to one branch and is therefore optional. `intervalMillis` is the
|
|
13215
|
+
* pending branch, and the five after it are the approved branch — the same
|
|
13216
|
+
* fields `LoginResponse` carries, because an approved poll is the login the
|
|
13217
|
+
* approval produced. `deviceAuthorization.pollStatusRule` states the pairing
|
|
13218
|
+
* the grammar cannot.
|
|
13219
|
+
*/
|
|
13220
|
+
{
|
|
13221
|
+
name: "PollDeviceAuthResponse",
|
|
13222
|
+
fields: [
|
|
13223
|
+
required("status", "DeviceAuthPollStatus"),
|
|
13224
|
+
optional("intervalMillis", "integer"),
|
|
13225
|
+
optional("userId", "string"),
|
|
13226
|
+
optional("publicId", "string"),
|
|
13227
|
+
optional("email", "string"),
|
|
13228
|
+
optional("phone", "string"),
|
|
13229
|
+
optional("passwordChangeRequired", "boolean")
|
|
13230
|
+
]
|
|
13231
|
+
},
|
|
13232
|
+
/**
|
|
13233
|
+
* Info, approve and deny each declare their own request type although all
|
|
13234
|
+
* three carry nothing but `userCode`. An operation's request shape is its
|
|
13235
|
+
* own: a field added to one of them later must not appear on the other two
|
|
13236
|
+
* by accident, which is what a shared type would do.
|
|
13237
|
+
*/
|
|
13238
|
+
{
|
|
13239
|
+
name: "DeviceAuthInfoRequest",
|
|
13240
|
+
fields: [
|
|
13241
|
+
required("userCode", "string")
|
|
13242
|
+
]
|
|
13243
|
+
},
|
|
13244
|
+
{
|
|
13245
|
+
name: "DeviceAuthInfoResponse",
|
|
13246
|
+
fields: [
|
|
13247
|
+
optional("deviceName", "string"),
|
|
13248
|
+
optional("platform", "KeyPlatform"),
|
|
13249
|
+
required("fingerprintPrefix", "string"),
|
|
13250
|
+
required("requestedAtMillis", "integer"),
|
|
13251
|
+
required("expiresAtMillis", "integer")
|
|
13252
|
+
]
|
|
13253
|
+
},
|
|
13254
|
+
{
|
|
13255
|
+
name: "ApproveDeviceAuthRequest",
|
|
13256
|
+
fields: [
|
|
13257
|
+
required("userCode", "string")
|
|
13258
|
+
]
|
|
13259
|
+
},
|
|
13260
|
+
{
|
|
13261
|
+
name: "DenyDeviceAuthRequest",
|
|
13262
|
+
fields: [
|
|
13263
|
+
required("userCode", "string")
|
|
13264
|
+
]
|
|
12589
13265
|
}
|
|
12590
13266
|
];
|
|
12591
13267
|
var CONTRACT_ENUMS = [
|
|
12592
|
-
{ name: "KeyAlgorithm", values: [...KEY_ALGORITHM] }
|
|
13268
|
+
{ name: "KeyAlgorithm", values: [...KEY_ALGORITHM] },
|
|
13269
|
+
{ name: "KeyPlatform", values: [...KEY_PLATFORM] },
|
|
13270
|
+
{ name: "DeviceAuthPollStatus", values: ["pending", "approved"] }
|
|
12593
13271
|
];
|
|
12594
13272
|
var BUNDLE_FILENAME = "spfn-mobile-contract.json";
|
|
12595
13273
|
var BUNDLE_REPO_PATH = `contracts/mobile/${BUNDLE_FILENAME}`;
|
|
@@ -12623,7 +13301,7 @@ import {
|
|
|
12623
13301
|
userProfilesRepository as userProfilesRepository2,
|
|
12624
13302
|
getPendingDeletionInfo as getPendingDeletionInfo2
|
|
12625
13303
|
} from "@spfn/auth/server";
|
|
12626
|
-
import { AccountDisabledError as
|
|
13304
|
+
import { AccountDisabledError as AccountDisabledError4, AccountPendingDeletionError as AccountPendingDeletionError4 } from "@spfn/auth/errors";
|
|
12627
13305
|
|
|
12628
13306
|
// src/server/client-proof/refusal-response.ts
|
|
12629
13307
|
function clientProofRefusalResponse(c, refusal) {
|
|
@@ -12718,9 +13396,9 @@ async function resolveAuthenticatedUser(userId) {
|
|
|
12718
13396
|
if (user.status !== "active") {
|
|
12719
13397
|
if (user.status === "pending_deletion") {
|
|
12720
13398
|
const pending = await getPendingDeletionInfo2(user.id);
|
|
12721
|
-
throw new
|
|
13399
|
+
throw new AccountPendingDeletionError4({ purgeScheduledAt: pending?.purgeScheduledAt.toISOString() });
|
|
12722
13400
|
}
|
|
12723
|
-
throw new
|
|
13401
|
+
throw new AccountDisabledError4({ status: user.status });
|
|
12724
13402
|
}
|
|
12725
13403
|
return { user, role: role?.name ?? null, locale };
|
|
12726
13404
|
}
|
|
@@ -12739,6 +13417,9 @@ var ClientProofRefusalError = class extends SerializableError {
|
|
|
12739
13417
|
function refusalError(refusal) {
|
|
12740
13418
|
return new ClientProofRefusalError(refusal);
|
|
12741
13419
|
}
|
|
13420
|
+
function clientProofRefusalOf(err) {
|
|
13421
|
+
return err instanceof ClientProofRefusalError ? err.refusal : null;
|
|
13422
|
+
}
|
|
12742
13423
|
async function runAuthProfile(c) {
|
|
12743
13424
|
try {
|
|
12744
13425
|
const verifier = selectAuthProfile(c);
|
|
@@ -12884,8 +13565,172 @@ function registerAuthProfile(profileId, verifier) {
|
|
|
12884
13565
|
AUTH_PROFILE_VERIFIERS.set(profileId, { verify: verifier.verify.bind(verifier) });
|
|
12885
13566
|
}
|
|
12886
13567
|
|
|
13568
|
+
// src/server/middleware/machine-principals.ts
|
|
13569
|
+
import { decodeProtectedHeader } from "jose";
|
|
13570
|
+
import { defineMiddleware } from "@spfn/core/route";
|
|
13571
|
+
import { ForbiddenError as ForbiddenError2, UnauthorizedError as UnauthorizedError2 } from "@spfn/core/errors";
|
|
13572
|
+
function getMachinePrincipal(c) {
|
|
13573
|
+
return c.get("machinePrincipal") ?? null;
|
|
13574
|
+
}
|
|
13575
|
+
var TOKEN_PREFIX_VERIFIERS = [];
|
|
13576
|
+
var KID_PREFIX_VERIFIERS = [];
|
|
13577
|
+
var REGISTERED_IDS = /* @__PURE__ */ new Set();
|
|
13578
|
+
function readDiscriminator(match) {
|
|
13579
|
+
const tokenPrefix = match?.tokenPrefix;
|
|
13580
|
+
const kidPrefix = match?.kidPrefix;
|
|
13581
|
+
if (tokenPrefix !== void 0 && kidPrefix !== void 0) {
|
|
13582
|
+
return null;
|
|
13583
|
+
}
|
|
13584
|
+
if (typeof tokenPrefix === "string" && tokenPrefix.length > 0) {
|
|
13585
|
+
return { kind: "tokenPrefix", prefix: tokenPrefix };
|
|
13586
|
+
}
|
|
13587
|
+
if (typeof kidPrefix === "string" && kidPrefix.length > 0) {
|
|
13588
|
+
return { kind: "kidPrefix", prefix: kidPrefix };
|
|
13589
|
+
}
|
|
13590
|
+
return null;
|
|
13591
|
+
}
|
|
13592
|
+
function prefixesCollide(a, b) {
|
|
13593
|
+
return a.startsWith(b) || b.startsWith(a);
|
|
13594
|
+
}
|
|
13595
|
+
function registerMachineVerifier(reg) {
|
|
13596
|
+
if (typeof reg?.id !== "string" || reg.id.length === 0) {
|
|
13597
|
+
throw new Error("registerMachineVerifier: id must be a non-empty string");
|
|
13598
|
+
}
|
|
13599
|
+
if (typeof reg.verify !== "function") {
|
|
13600
|
+
throw new Error(`registerMachineVerifier: '${reg.id}' needs a verifier with a callable verify(token, c)`);
|
|
13601
|
+
}
|
|
13602
|
+
const discriminator = readDiscriminator(reg.match);
|
|
13603
|
+
if (discriminator === null) {
|
|
13604
|
+
throw new Error(`registerMachineVerifier: '${reg.id}' needs exactly one non-empty discriminator \u2014 { tokenPrefix } or { kidPrefix }`);
|
|
13605
|
+
}
|
|
13606
|
+
if (REGISTERED_IDS.has(reg.id)) {
|
|
13607
|
+
throw new Error(`registerMachineVerifier: '${reg.id}' is already registered`);
|
|
13608
|
+
}
|
|
13609
|
+
const peers = discriminator.kind === "tokenPrefix" ? TOKEN_PREFIX_VERIFIERS : KID_PREFIX_VERIFIERS;
|
|
13610
|
+
const shadowed = peers.find((peer) => prefixesCollide(peer.prefix, discriminator.prefix));
|
|
13611
|
+
if (shadowed !== void 0) {
|
|
13612
|
+
throw new Error(
|
|
13613
|
+
`registerMachineVerifier: '${reg.id}' ${discriminator.kind} '${discriminator.prefix}' collides with '${shadowed.id}' \u2014 one prefix would shadow the other`
|
|
13614
|
+
);
|
|
13615
|
+
}
|
|
13616
|
+
peers.push({ id: reg.id, prefix: discriminator.prefix, verify: reg.verify.bind(reg) });
|
|
13617
|
+
REGISTERED_IDS.add(reg.id);
|
|
13618
|
+
}
|
|
13619
|
+
function findMachineVerifier(token) {
|
|
13620
|
+
const byTokenPrefix = TOKEN_PREFIX_VERIFIERS.find((entry) => token.startsWith(entry.prefix));
|
|
13621
|
+
if (byTokenPrefix !== void 0) {
|
|
13622
|
+
return byTokenPrefix;
|
|
13623
|
+
}
|
|
13624
|
+
if (KID_PREFIX_VERIFIERS.length === 0) {
|
|
13625
|
+
return null;
|
|
13626
|
+
}
|
|
13627
|
+
const kid = readProtectedKid(token);
|
|
13628
|
+
if (kid === null) {
|
|
13629
|
+
return null;
|
|
13630
|
+
}
|
|
13631
|
+
return KID_PREFIX_VERIFIERS.find((entry) => kid.startsWith(entry.prefix)) ?? null;
|
|
13632
|
+
}
|
|
13633
|
+
function matchesMachineDiscriminator(token) {
|
|
13634
|
+
return findMachineVerifier(token) !== null;
|
|
13635
|
+
}
|
|
13636
|
+
function readProtectedKid(token) {
|
|
13637
|
+
try {
|
|
13638
|
+
const { kid } = decodeProtectedHeader(token);
|
|
13639
|
+
return typeof kid === "string" ? kid : null;
|
|
13640
|
+
} catch {
|
|
13641
|
+
return null;
|
|
13642
|
+
}
|
|
13643
|
+
}
|
|
13644
|
+
var MACHINE_REFUSAL_MESSAGE = "Machine authentication required: Authorization: Bearer <token>";
|
|
13645
|
+
function machineRefusal() {
|
|
13646
|
+
return new UnauthorizedError2({ message: MACHINE_REFUSAL_MESSAGE });
|
|
13647
|
+
}
|
|
13648
|
+
var machineAuth = defineMiddleware("machineAuth", async (c, next) => {
|
|
13649
|
+
const refused = profileChannelRefusal(c);
|
|
13650
|
+
if (refused !== null) {
|
|
13651
|
+
return refused;
|
|
13652
|
+
}
|
|
13653
|
+
const token = extractBearer(c.req.header("Authorization"));
|
|
13654
|
+
if (token === null) {
|
|
13655
|
+
throw machineRefusal();
|
|
13656
|
+
}
|
|
13657
|
+
const entry = findMachineVerifier(token);
|
|
13658
|
+
if (entry === null) {
|
|
13659
|
+
throw machineRefusal();
|
|
13660
|
+
}
|
|
13661
|
+
const principal = await verifiedPrincipal(entry, token, c);
|
|
13662
|
+
if (principal === null) {
|
|
13663
|
+
throw machineRefusal();
|
|
13664
|
+
}
|
|
13665
|
+
c.set("machinePrincipal", principal);
|
|
13666
|
+
await next();
|
|
13667
|
+
return void 0;
|
|
13668
|
+
}, { skips: ["auth"] });
|
|
13669
|
+
function profileChannelRefusal(c) {
|
|
13670
|
+
try {
|
|
13671
|
+
selectAuthProfile(c);
|
|
13672
|
+
return null;
|
|
13673
|
+
} catch (err) {
|
|
13674
|
+
const refusal = clientProofRefusalOf(err);
|
|
13675
|
+
if (refusal === null) {
|
|
13676
|
+
throw err;
|
|
13677
|
+
}
|
|
13678
|
+
return clientProofRefusalResponse(c, refusal);
|
|
13679
|
+
}
|
|
13680
|
+
}
|
|
13681
|
+
async function verifiedPrincipal(entry, token, c) {
|
|
13682
|
+
try {
|
|
13683
|
+
const principal = copyPrincipal(await entry.verify(token, c), entry.id);
|
|
13684
|
+
if (principal === null) {
|
|
13685
|
+
authLogger.middleware.error(`machine verifier '${entry.id}' resolved no principal`);
|
|
13686
|
+
}
|
|
13687
|
+
return principal;
|
|
13688
|
+
} catch (err) {
|
|
13689
|
+
authLogger.middleware.error(`machine verifier '${entry.id}' refused or failed`, err);
|
|
13690
|
+
return null;
|
|
13691
|
+
}
|
|
13692
|
+
}
|
|
13693
|
+
function copyPrincipal(resolved, scheme) {
|
|
13694
|
+
const subjectType = resolved?.subjectType;
|
|
13695
|
+
const subjectId = resolved?.subjectId;
|
|
13696
|
+
const scopes = resolved?.scopes;
|
|
13697
|
+
const claims = resolved?.claims;
|
|
13698
|
+
if (typeof subjectType !== "string" || subjectType.length === 0 || typeof subjectId !== "string" || subjectId.length === 0 || !Array.isArray(scopes)) {
|
|
13699
|
+
return null;
|
|
13700
|
+
}
|
|
13701
|
+
return {
|
|
13702
|
+
subjectType,
|
|
13703
|
+
subjectId,
|
|
13704
|
+
scopes: [...scopes],
|
|
13705
|
+
claims: claims === void 0 ? void 0 : structuredClone(claims),
|
|
13706
|
+
scheme
|
|
13707
|
+
};
|
|
13708
|
+
}
|
|
13709
|
+
var requireMachineScope = defineMiddleware(
|
|
13710
|
+
"machineScope",
|
|
13711
|
+
(...scopes) => async (c, next) => {
|
|
13712
|
+
const principal = getMachinePrincipal(c);
|
|
13713
|
+
if (!principal) {
|
|
13714
|
+
throw machineRefusal();
|
|
13715
|
+
}
|
|
13716
|
+
const granted = new Set(principal.scopes);
|
|
13717
|
+
const missing = scopes.filter((scope) => !granted.has(scope));
|
|
13718
|
+
if (missing.length > 0) {
|
|
13719
|
+
throw new ForbiddenError2({ message: `Machine principal lacks scope: ${missing.join(", ")}` });
|
|
13720
|
+
}
|
|
13721
|
+
await next();
|
|
13722
|
+
}
|
|
13723
|
+
);
|
|
13724
|
+
function extractBearer(header) {
|
|
13725
|
+
if (!header || !header.startsWith("Bearer ")) {
|
|
13726
|
+
return null;
|
|
13727
|
+
}
|
|
13728
|
+
return header.substring(7);
|
|
13729
|
+
}
|
|
13730
|
+
|
|
12887
13731
|
// src/server/middleware/authenticate.ts
|
|
12888
|
-
var
|
|
13732
|
+
var INVALID_TOKEN_MESSAGE = "Invalid token: missing keyId";
|
|
13733
|
+
var authenticate = defineMiddleware2("auth", async (c, next) => {
|
|
12889
13734
|
const profile = await runAuthProfile(c);
|
|
12890
13735
|
if (profile.kind === "refused") {
|
|
12891
13736
|
return profile.response;
|
|
@@ -12901,17 +13746,21 @@ var authenticate = defineMiddleware("auth", async (c, next) => {
|
|
|
12901
13746
|
headers: c.req.header(),
|
|
12902
13747
|
path: c.req.path
|
|
12903
13748
|
});
|
|
12904
|
-
throw new
|
|
13749
|
+
throw new UnauthorizedError3({ message: "Authentication header missing or invalid: Bearer {token}" });
|
|
12905
13750
|
}
|
|
12906
13751
|
const token = authHeader.substring(7);
|
|
13752
|
+
if (matchesMachineDiscriminator(token)) {
|
|
13753
|
+
authLogger3.middleware.warn("Machine credential presented to the user path \u2014 refused", { path: c.req.path });
|
|
13754
|
+
throw new UnauthorizedError3({ message: INVALID_TOKEN_MESSAGE });
|
|
13755
|
+
}
|
|
12907
13756
|
const decoded = decodeToken2(token);
|
|
12908
13757
|
if (!decoded || !decoded.keyId) {
|
|
12909
|
-
throw new
|
|
13758
|
+
throw new UnauthorizedError3({ message: INVALID_TOKEN_MESSAGE });
|
|
12910
13759
|
}
|
|
12911
13760
|
const keyId = decoded.keyId;
|
|
12912
13761
|
const keyRecord = await keysRepository3.findActiveByKeyId(keyId);
|
|
12913
13762
|
if (!keyRecord) {
|
|
12914
|
-
throw new
|
|
13763
|
+
throw new UnauthorizedError3({ message: "Invalid or revoked key" });
|
|
12915
13764
|
}
|
|
12916
13765
|
if (keyRecord.expiresAt && /* @__PURE__ */ new Date() > keyRecord.expiresAt) {
|
|
12917
13766
|
throw new KeyExpiredError();
|
|
@@ -12932,7 +13781,7 @@ var authenticate = defineMiddleware("auth", async (c, next) => {
|
|
|
12932
13781
|
throw new InvalidTokenError({ message: "Invalid token signature" });
|
|
12933
13782
|
}
|
|
12934
13783
|
}
|
|
12935
|
-
throw new
|
|
13784
|
+
throw new UnauthorizedError3({ message: "Authentication failed" });
|
|
12936
13785
|
}
|
|
12937
13786
|
const { user, role, locale } = await resolveAuthenticatedUser(keyRecord.userId);
|
|
12938
13787
|
keysRepository3.updateLastUsedById(keyRecord.id, readContextClientIdentity(c)).catch((err) => authLogger3.middleware.error("Failed to update lastUsedAt", err));
|
|
@@ -12958,7 +13807,7 @@ var authenticate = defineMiddleware("auth", async (c, next) => {
|
|
|
12958
13807
|
await next();
|
|
12959
13808
|
return void 0;
|
|
12960
13809
|
});
|
|
12961
|
-
var optionalAuth =
|
|
13810
|
+
var optionalAuth = defineMiddleware2("optionalAuth", async (c, next) => {
|
|
12962
13811
|
const profile = await runAuthProfile(c);
|
|
12963
13812
|
if (profile.kind === "refused") {
|
|
12964
13813
|
return profile.response;
|
|
@@ -12974,6 +13823,10 @@ var optionalAuth = defineMiddleware("optionalAuth", async (c, next) => {
|
|
|
12974
13823
|
return void 0;
|
|
12975
13824
|
}
|
|
12976
13825
|
const token = authHeader.substring(7);
|
|
13826
|
+
if (matchesMachineDiscriminator(token)) {
|
|
13827
|
+
authLogger3.middleware.warn("Machine credential presented to the user path \u2014 refused", { path: c.req.path });
|
|
13828
|
+
throw new UnauthorizedError3({ message: INVALID_TOKEN_MESSAGE });
|
|
13829
|
+
}
|
|
12977
13830
|
try {
|
|
12978
13831
|
const decoded = decodeToken2(token);
|
|
12979
13832
|
if (!decoded || !decoded.keyId) {
|
|
@@ -13020,11 +13873,11 @@ var optionalAuth = defineMiddleware("optionalAuth", async (c, next) => {
|
|
|
13020
13873
|
}, { skips: ["auth"] });
|
|
13021
13874
|
|
|
13022
13875
|
// src/server/middleware/require-permission.ts
|
|
13023
|
-
import { defineMiddleware as
|
|
13024
|
-
import { ForbiddenError as
|
|
13876
|
+
import { defineMiddleware as defineMiddleware3 } from "@spfn/core/route";
|
|
13877
|
+
import { ForbiddenError as ForbiddenError3 } from "@spfn/core/errors";
|
|
13025
13878
|
import { InsufficientPermissionsError } from "@spfn/auth/errors";
|
|
13026
13879
|
import { getAuth as getAuth2, hasAllPermissions as hasAllPermissions2, hasAnyPermission as hasAnyPermission2, authLogger as authLogger4 } from "@spfn/auth/server";
|
|
13027
|
-
var requirePermissions =
|
|
13880
|
+
var requirePermissions = defineMiddleware3(
|
|
13028
13881
|
"permission",
|
|
13029
13882
|
(...permissionNames) => async (c, next) => {
|
|
13030
13883
|
const auth = getAuth2(c);
|
|
@@ -13033,7 +13886,7 @@ var requirePermissions = defineMiddleware2(
|
|
|
13033
13886
|
permissions: permissionNames,
|
|
13034
13887
|
path: c.req.path
|
|
13035
13888
|
});
|
|
13036
|
-
throw new
|
|
13889
|
+
throw new ForbiddenError3({ message: "Authentication required" });
|
|
13037
13890
|
}
|
|
13038
13891
|
const { userId } = auth;
|
|
13039
13892
|
const allowed = await hasAllPermissions2(userId, permissionNames);
|
|
@@ -13052,7 +13905,7 @@ var requirePermissions = defineMiddleware2(
|
|
|
13052
13905
|
await next();
|
|
13053
13906
|
}
|
|
13054
13907
|
);
|
|
13055
|
-
var requireAnyPermission =
|
|
13908
|
+
var requireAnyPermission = defineMiddleware3(
|
|
13056
13909
|
"anyPermission",
|
|
13057
13910
|
(...permissionNames) => async (c, next) => {
|
|
13058
13911
|
const auth = getAuth2(c);
|
|
@@ -13061,7 +13914,7 @@ var requireAnyPermission = defineMiddleware2(
|
|
|
13061
13914
|
permissions: permissionNames,
|
|
13062
13915
|
path: c.req.path
|
|
13063
13916
|
});
|
|
13064
|
-
throw new
|
|
13917
|
+
throw new ForbiddenError3({ message: "Authentication required" });
|
|
13065
13918
|
}
|
|
13066
13919
|
const { userId } = auth;
|
|
13067
13920
|
const allowed = await hasAnyPermission2(userId, permissionNames);
|
|
@@ -13082,11 +13935,11 @@ var requireAnyPermission = defineMiddleware2(
|
|
|
13082
13935
|
);
|
|
13083
13936
|
|
|
13084
13937
|
// src/server/middleware/require-role.ts
|
|
13085
|
-
import { defineMiddleware as
|
|
13938
|
+
import { defineMiddleware as defineMiddleware4 } from "@spfn/core/route";
|
|
13086
13939
|
import { getAuth as getAuth3, authLogger as authLogger5 } from "@spfn/auth/server";
|
|
13087
|
-
import { ForbiddenError as
|
|
13940
|
+
import { ForbiddenError as ForbiddenError4 } from "@spfn/core/errors";
|
|
13088
13941
|
import { InsufficientRoleError } from "@spfn/auth/errors";
|
|
13089
|
-
var requireRole =
|
|
13942
|
+
var requireRole = defineMiddleware4(
|
|
13090
13943
|
"role",
|
|
13091
13944
|
(...roleNames) => async (c, next) => {
|
|
13092
13945
|
const auth = getAuth3(c);
|
|
@@ -13095,7 +13948,7 @@ var requireRole = defineMiddleware3(
|
|
|
13095
13948
|
roles: roleNames,
|
|
13096
13949
|
path: c.req.path
|
|
13097
13950
|
});
|
|
13098
|
-
throw new
|
|
13951
|
+
throw new ForbiddenError4({ message: "Authentication required" });
|
|
13099
13952
|
}
|
|
13100
13953
|
const { userId, role: userRole } = auth;
|
|
13101
13954
|
if (!userRole || !roleNames.includes(userRole)) {
|
|
@@ -13117,11 +13970,11 @@ var requireRole = defineMiddleware3(
|
|
|
13117
13970
|
);
|
|
13118
13971
|
|
|
13119
13972
|
// src/server/middleware/role-guard.ts
|
|
13120
|
-
import { defineMiddleware as
|
|
13973
|
+
import { defineMiddleware as defineMiddleware5 } from "@spfn/core/route";
|
|
13121
13974
|
import { getAuth as getAuth4, authLogger as authLogger6 } from "@spfn/auth/server";
|
|
13122
|
-
import { ForbiddenError as
|
|
13975
|
+
import { ForbiddenError as ForbiddenError5 } from "@spfn/core/errors";
|
|
13123
13976
|
import { InsufficientRoleError as InsufficientRoleError2 } from "@spfn/auth/errors";
|
|
13124
|
-
var roleGuard =
|
|
13977
|
+
var roleGuard = defineMiddleware5(
|
|
13125
13978
|
"roleGuard",
|
|
13126
13979
|
(options) => async (c, next) => {
|
|
13127
13980
|
const { allow, deny } = options;
|
|
@@ -13133,7 +13986,7 @@ var roleGuard = defineMiddleware4(
|
|
|
13133
13986
|
authLogger6.middleware.warn("Role guard failed: not authenticated", {
|
|
13134
13987
|
path: c.req.path
|
|
13135
13988
|
});
|
|
13136
|
-
throw new
|
|
13989
|
+
throw new ForbiddenError5({ message: "Authentication required" });
|
|
13137
13990
|
}
|
|
13138
13991
|
const { userId, role: userRole } = auth;
|
|
13139
13992
|
if (deny && deny.length > 0) {
|
|
@@ -13169,28 +14022,28 @@ var roleGuard = defineMiddleware4(
|
|
|
13169
14022
|
);
|
|
13170
14023
|
|
|
13171
14024
|
// src/server/middleware/one-time-token-auth.ts
|
|
13172
|
-
import { defineMiddleware as
|
|
13173
|
-
import { UnauthorizedError as
|
|
14025
|
+
import { defineMiddleware as defineMiddleware6 } from "@spfn/core/route";
|
|
14026
|
+
import { UnauthorizedError as UnauthorizedError4 } from "@spfn/core/errors";
|
|
13174
14027
|
import { usersRepository as usersRepository4, userProfilesRepository as userProfilesRepository4 } from "@spfn/auth/server";
|
|
13175
|
-
var oneTimeTokenAuth =
|
|
14028
|
+
var oneTimeTokenAuth = defineMiddleware6("oneTimeTokenAuth", async (c, next) => {
|
|
13176
14029
|
const token = c.req.query("token") ?? extractOTTHeader(c.req.header("Authorization"));
|
|
13177
14030
|
if (!token) {
|
|
13178
|
-
throw new
|
|
14031
|
+
throw new UnauthorizedError4({ message: "One-time token required: ?token=xxx or Authorization: OTT xxx" });
|
|
13179
14032
|
}
|
|
13180
14033
|
const userId = await verifyOneTimeTokenService(token);
|
|
13181
14034
|
if (!userId) {
|
|
13182
|
-
throw new
|
|
14035
|
+
throw new UnauthorizedError4({ message: "Invalid or expired one-time token" });
|
|
13183
14036
|
}
|
|
13184
14037
|
const [result, locale] = await Promise.all([
|
|
13185
14038
|
usersRepository4.findByIdWithRole(Number(userId)),
|
|
13186
14039
|
userProfilesRepository4.findLocaleByUserId(Number(userId))
|
|
13187
14040
|
]);
|
|
13188
14041
|
if (!result) {
|
|
13189
|
-
throw new
|
|
14042
|
+
throw new UnauthorizedError4({ message: "User not found" });
|
|
13190
14043
|
}
|
|
13191
14044
|
const { user, role } = result;
|
|
13192
14045
|
if (user.status !== "active") {
|
|
13193
|
-
throw new
|
|
14046
|
+
throw new UnauthorizedError4({ message: "Account is not active" });
|
|
13194
14047
|
}
|
|
13195
14048
|
c.set("auth", {
|
|
13196
14049
|
user,
|
|
@@ -13211,39 +14064,39 @@ function extractOTTHeader(header) {
|
|
|
13211
14064
|
}
|
|
13212
14065
|
|
|
13213
14066
|
// src/server/middleware/ops-token-auth.ts
|
|
13214
|
-
import { defineMiddleware as
|
|
13215
|
-
import { ForbiddenError as
|
|
14067
|
+
import { defineMiddleware as defineMiddleware7 } from "@spfn/core/route";
|
|
14068
|
+
import { ForbiddenError as ForbiddenError6, UnauthorizedError as UnauthorizedError5 } from "@spfn/core/errors";
|
|
13216
14069
|
function getOpsToken(c) {
|
|
13217
14070
|
return c.get("opsToken") ?? null;
|
|
13218
14071
|
}
|
|
13219
|
-
var opsTokenAuth =
|
|
13220
|
-
const token =
|
|
14072
|
+
var opsTokenAuth = defineMiddleware7("opsTokenAuth", async (c, next) => {
|
|
14073
|
+
const token = extractBearer2(c.req.header("Authorization"));
|
|
13221
14074
|
if (!token) {
|
|
13222
|
-
throw new
|
|
14075
|
+
throw new UnauthorizedError5({ message: "Ops token required: Authorization: Bearer <token>" });
|
|
13223
14076
|
}
|
|
13224
14077
|
const verified = await verifyOpsTokenService(token);
|
|
13225
14078
|
if (!verified) {
|
|
13226
|
-
throw new
|
|
14079
|
+
throw new UnauthorizedError5({ message: "Invalid ops token" });
|
|
13227
14080
|
}
|
|
13228
14081
|
c.set("opsToken", verified);
|
|
13229
14082
|
await next();
|
|
13230
14083
|
}, { skips: ["auth"] });
|
|
13231
|
-
var requireOpsScope =
|
|
14084
|
+
var requireOpsScope = defineMiddleware7(
|
|
13232
14085
|
"opsScope",
|
|
13233
14086
|
(...scopes) => async (c, next) => {
|
|
13234
14087
|
const token = getOpsToken(c);
|
|
13235
14088
|
if (!token) {
|
|
13236
|
-
throw new
|
|
14089
|
+
throw new UnauthorizedError5({ message: "Ops token required: Authorization: Bearer <token>" });
|
|
13237
14090
|
}
|
|
13238
14091
|
const granted = new Set(token.scopes);
|
|
13239
14092
|
const missing = scopes.filter((scope) => !granted.has(scope) && !granted.has("*"));
|
|
13240
14093
|
if (missing.length > 0) {
|
|
13241
|
-
throw new
|
|
14094
|
+
throw new ForbiddenError6({ message: `Ops token lacks scope: ${missing.join(", ")}` });
|
|
13242
14095
|
}
|
|
13243
14096
|
await next();
|
|
13244
14097
|
}
|
|
13245
14098
|
);
|
|
13246
|
-
function
|
|
14099
|
+
function extractBearer2(header) {
|
|
13247
14100
|
if (!header || !header.startsWith("Bearer ")) {
|
|
13248
14101
|
return null;
|
|
13249
14102
|
}
|
|
@@ -14038,7 +14891,7 @@ var oauthRouter = defineRouter4({
|
|
|
14038
14891
|
|
|
14039
14892
|
// src/server/routes/admin/index.ts
|
|
14040
14893
|
init_esm();
|
|
14041
|
-
import { ForbiddenError as
|
|
14894
|
+
import { ForbiddenError as ForbiddenError7 } from "@spfn/core/errors";
|
|
14042
14895
|
import { route as route5 } from "@spfn/core/route";
|
|
14043
14896
|
var listRoles = route5.get("/_auth/admin/roles").input({
|
|
14044
14897
|
query: Type.Object({
|
|
@@ -14108,11 +14961,11 @@ var updateUserRole = route5.patch("/_auth/admin/users/:userId/role").input({
|
|
|
14108
14961
|
const { params, body } = await c.data();
|
|
14109
14962
|
const auth = getAuth(c);
|
|
14110
14963
|
if (params.userId === Number(auth.userId)) {
|
|
14111
|
-
throw new
|
|
14964
|
+
throw new ForbiddenError7({ message: "Cannot change your own role" });
|
|
14112
14965
|
}
|
|
14113
14966
|
const targetRole = await getUserRole(params.userId);
|
|
14114
14967
|
if (targetRole === "superadmin") {
|
|
14115
|
-
throw new
|
|
14968
|
+
throw new ForbiddenError7({ message: "Cannot modify superadmin role" });
|
|
14116
14969
|
}
|
|
14117
14970
|
await assertCanAssignRole(auth.userId, body.roleId);
|
|
14118
14971
|
await updateUserService(params.userId, { roleId: body.roleId });
|
|
@@ -14236,6 +15089,12 @@ var mainAuthRouter = defineRouter6({
|
|
|
14236
15089
|
confirmSignupLink,
|
|
14237
15090
|
completeSignup,
|
|
14238
15091
|
login,
|
|
15092
|
+
// Device-code login routes
|
|
15093
|
+
startDeviceAuth,
|
|
15094
|
+
pollDeviceAuth,
|
|
15095
|
+
getDeviceAuthInfo,
|
|
15096
|
+
approveDeviceAuth,
|
|
15097
|
+
denyDeviceAuth,
|
|
14239
15098
|
logout,
|
|
14240
15099
|
rotateKey,
|
|
14241
15100
|
listKeys,
|
|
@@ -14479,15 +15338,64 @@ async function shouldRefreshSession(jwt4, thresholdHours = 24) {
|
|
|
14479
15338
|
return hoursRemaining < thresholdHours;
|
|
14480
15339
|
}
|
|
14481
15340
|
|
|
14482
|
-
// src/server/
|
|
15341
|
+
// src/server/lib/csrf.ts
|
|
14483
15342
|
import { env as env14 } from "@spfn/auth/config";
|
|
15343
|
+
var CSRF_HEADER = "x-spfn-csrf";
|
|
15344
|
+
var CSRF_SUBKEY_LABEL = "spfn-auth-csrf-token-v1";
|
|
15345
|
+
var MAX_CANDIDATES = 32;
|
|
15346
|
+
function sessionSecret() {
|
|
15347
|
+
const secret = env14.SPFN_AUTH_SESSION_SECRET;
|
|
15348
|
+
if (!secret) {
|
|
15349
|
+
throw new Error(
|
|
15350
|
+
"SPFN_AUTH_SESSION_SECRET is required for CSRF protection. Set it (sessions need it anyway), or set SPFN_AUTH_CSRF=off."
|
|
15351
|
+
);
|
|
15352
|
+
}
|
|
15353
|
+
return secret;
|
|
15354
|
+
}
|
|
15355
|
+
async function hmacSha256(key, message) {
|
|
15356
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
15357
|
+
"raw",
|
|
15358
|
+
key.buffer,
|
|
15359
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
15360
|
+
false,
|
|
15361
|
+
["sign"]
|
|
15362
|
+
);
|
|
15363
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(message));
|
|
15364
|
+
return new Uint8Array(signature);
|
|
15365
|
+
}
|
|
15366
|
+
function toHex(bytes) {
|
|
15367
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
15368
|
+
}
|
|
15369
|
+
async function deriveCsrfToken(keyId) {
|
|
15370
|
+
const subkey = await hmacSha256(new TextEncoder().encode(sessionSecret()), CSRF_SUBKEY_LABEL);
|
|
15371
|
+
return toHex(await hmacSha256(subkey, keyId));
|
|
15372
|
+
}
|
|
15373
|
+
function timingSafeEqualString(a, b) {
|
|
15374
|
+
if (a.length !== b.length) {
|
|
15375
|
+
return false;
|
|
15376
|
+
}
|
|
15377
|
+
let difference = 0;
|
|
15378
|
+
for (let i = 0; i < a.length; i++) {
|
|
15379
|
+
difference |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
15380
|
+
}
|
|
15381
|
+
return difference === 0;
|
|
15382
|
+
}
|
|
15383
|
+
function matchesCsrfToken(expected, presented) {
|
|
15384
|
+
if (!presented) {
|
|
15385
|
+
return false;
|
|
15386
|
+
}
|
|
15387
|
+
return presented.split(",", MAX_CANDIDATES).some((candidate) => timingSafeEqualString(expected, candidate.trim()));
|
|
15388
|
+
}
|
|
15389
|
+
|
|
15390
|
+
// src/server/setup.ts
|
|
15391
|
+
import { env as env15 } from "@spfn/auth/config";
|
|
14484
15392
|
import { getRoleByName as getRoleByName2 } from "@spfn/auth/server";
|
|
14485
15393
|
init_repositories();
|
|
14486
15394
|
function parseAdminAccounts() {
|
|
14487
15395
|
const accounts = [];
|
|
14488
|
-
if (
|
|
15396
|
+
if (env15.SPFN_AUTH_ADMIN_ACCOUNTS) {
|
|
14489
15397
|
try {
|
|
14490
|
-
const accountsJson =
|
|
15398
|
+
const accountsJson = env15.SPFN_AUTH_ADMIN_ACCOUNTS;
|
|
14491
15399
|
const parsed = JSON.parse(accountsJson);
|
|
14492
15400
|
if (!Array.isArray(parsed)) {
|
|
14493
15401
|
authLogger.setup.error("\u274C SPFN_AUTH_ADMIN_ACCOUNTS must be an array");
|
|
@@ -14514,11 +15422,11 @@ function parseAdminAccounts() {
|
|
|
14514
15422
|
return accounts;
|
|
14515
15423
|
}
|
|
14516
15424
|
}
|
|
14517
|
-
const adminEmails =
|
|
15425
|
+
const adminEmails = env15.SPFN_AUTH_ADMIN_EMAILS;
|
|
14518
15426
|
if (adminEmails) {
|
|
14519
15427
|
const emails = adminEmails.split(",").map((s) => s.trim());
|
|
14520
|
-
const passwords = (
|
|
14521
|
-
const roles2 = (
|
|
15428
|
+
const passwords = (env15.SPFN_AUTH_ADMIN_PASSWORDS || "").split(",").map((s) => s.trim());
|
|
15429
|
+
const roles2 = (env15.SPFN_AUTH_ADMIN_ROLES || "").split(",").map((s) => s.trim());
|
|
14522
15430
|
if (passwords.length !== emails.length) {
|
|
14523
15431
|
authLogger.setup.error("\u274C SPFN_AUTH_ADMIN_EMAILS and SPFN_AUTH_ADMIN_PASSWORDS length mismatch");
|
|
14524
15432
|
return accounts;
|
|
@@ -14540,8 +15448,8 @@ function parseAdminAccounts() {
|
|
|
14540
15448
|
}
|
|
14541
15449
|
return accounts;
|
|
14542
15450
|
}
|
|
14543
|
-
const adminEmail =
|
|
14544
|
-
const adminPassword =
|
|
15451
|
+
const adminEmail = env15.SPFN_AUTH_ADMIN_EMAIL;
|
|
15452
|
+
const adminPassword = env15.SPFN_AUTH_ADMIN_PASSWORD;
|
|
14545
15453
|
if (adminEmail && adminPassword) {
|
|
14546
15454
|
accounts.push({
|
|
14547
15455
|
email: adminEmail,
|
|
@@ -14605,6 +15513,7 @@ async function ensureAdminExists() {
|
|
|
14605
15513
|
// src/server/lifecycle.ts
|
|
14606
15514
|
function createAuthLifecycle(options = {}) {
|
|
14607
15515
|
configureDeletion(options.deletion);
|
|
15516
|
+
configureDeviceAuth(options.deviceAuth);
|
|
14608
15517
|
return {
|
|
14609
15518
|
/**
|
|
14610
15519
|
* Initialize auth system after database is ready
|
|
@@ -14653,20 +15562,28 @@ export {
|
|
|
14653
15562
|
AuthMetadataRepository,
|
|
14654
15563
|
AuthProviderSchema,
|
|
14655
15564
|
COOKIE_NAMES,
|
|
15565
|
+
CSRF_HEADER,
|
|
14656
15566
|
DEFAULT_DELETION_ALLOW_SELF_IMMEDIATE,
|
|
14657
15567
|
DEFAULT_DELETION_GRACE_PERIOD_DAYS,
|
|
14658
15568
|
DEFAULT_DELETION_PURGE_CRON,
|
|
14659
15569
|
DEFAULT_DELETION_PURGE_STRATEGY,
|
|
14660
15570
|
DEFAULT_DELETION_SEND_NOTIFICATIONS,
|
|
15571
|
+
DEFAULT_DEVICE_AUTH_INTERVAL_MS,
|
|
15572
|
+
DEFAULT_DEVICE_AUTH_TTL_MS,
|
|
15573
|
+
DEVICE_AUTH_STATUSES,
|
|
15574
|
+
DeviceAuthPollResponseSchema,
|
|
15575
|
+
DeviceAuthorizationsRepository,
|
|
14661
15576
|
DeviceNameSchema,
|
|
14662
15577
|
EmailSchema,
|
|
14663
15578
|
EnvironmentKeyringTokenCipher,
|
|
15579
|
+
FingerprintSchema,
|
|
14664
15580
|
INVITATION_STATUSES,
|
|
14665
15581
|
InvitationsRepository,
|
|
14666
15582
|
KEY_ALGORITHM,
|
|
14667
15583
|
KEY_DEVICE_NAME_MAX_LENGTH,
|
|
14668
15584
|
KEY_FINGERPRINT_PREFIX_LENGTH,
|
|
14669
15585
|
KEY_PLATFORM,
|
|
15586
|
+
KeyIdSchema,
|
|
14670
15587
|
KeysRepository,
|
|
14671
15588
|
OpsTokensRepository,
|
|
14672
15589
|
PURGE_STRATEGIES,
|
|
@@ -14674,14 +15591,18 @@ export {
|
|
|
14674
15591
|
PermissionsRepository,
|
|
14675
15592
|
PhoneSchema,
|
|
14676
15593
|
PlatformSchema,
|
|
15594
|
+
PublicKeySchema,
|
|
14677
15595
|
RolePermissionsRepository,
|
|
14678
15596
|
RolesRepository,
|
|
14679
15597
|
SOCIAL_PROVIDERS,
|
|
14680
15598
|
SignupLinkTokensRepository,
|
|
14681
15599
|
SocialAccountsRepository,
|
|
14682
15600
|
TargetTypeSchema,
|
|
15601
|
+
USER_CODE_ALPHABET,
|
|
15602
|
+
USER_CODE_LENGTH,
|
|
14683
15603
|
USER_STATUSES,
|
|
14684
15604
|
UnlinkNotifyRejection,
|
|
15605
|
+
UserCodeSchema,
|
|
14685
15606
|
UserPermissionsRepository,
|
|
14686
15607
|
UserProfilesRepository,
|
|
14687
15608
|
UsersRepository,
|
|
@@ -14694,6 +15615,7 @@ export {
|
|
|
14694
15615
|
accountDeletionRequestsRepository,
|
|
14695
15616
|
addPermissionToRole,
|
|
14696
15617
|
appleProvider,
|
|
15618
|
+
approveDeviceAuthService,
|
|
14697
15619
|
assertCanAssignRole,
|
|
14698
15620
|
authDeletionCancelledEvent,
|
|
14699
15621
|
authDeletionCompletedEvent,
|
|
@@ -14715,6 +15637,7 @@ export {
|
|
|
14715
15637
|
completeSignupService,
|
|
14716
15638
|
configureAuth,
|
|
14717
15639
|
configureDeletion,
|
|
15640
|
+
configureDeviceAuth,
|
|
14718
15641
|
configureOAuthTokenCipher,
|
|
14719
15642
|
confirmSignupLinkService,
|
|
14720
15643
|
createAuthDeletionJobRouter,
|
|
@@ -14727,20 +15650,31 @@ export {
|
|
|
14727
15650
|
decryptToken,
|
|
14728
15651
|
deleteInvitation,
|
|
14729
15652
|
deleteRole,
|
|
15653
|
+
denyDeviceAuthService,
|
|
15654
|
+
deriveCsrfToken,
|
|
15655
|
+
deviceAuthorizations,
|
|
15656
|
+
deviceAuthorizationsRepository,
|
|
14730
15657
|
encryptToken,
|
|
14731
15658
|
exchangeCodeForTokens,
|
|
14732
15659
|
expireOldInvitations,
|
|
15660
|
+
formatUserCode,
|
|
14733
15661
|
generateClientToken,
|
|
15662
|
+
generateDeviceCode,
|
|
14734
15663
|
generateKeyPair,
|
|
14735
15664
|
generateKeyPairES256,
|
|
14736
15665
|
generateKeyPairRS256,
|
|
14737
15666
|
generateOAuthNonce,
|
|
14738
15667
|
generateToken,
|
|
15668
|
+
generateUserCode,
|
|
14739
15669
|
getAllRoles,
|
|
14740
15670
|
getAuth,
|
|
14741
15671
|
getAuthConfig,
|
|
14742
15672
|
getAuthSessionService,
|
|
15673
|
+
getCsrfExemptPaths,
|
|
15674
|
+
getCsrfMode,
|
|
14743
15675
|
getDeletionConfig,
|
|
15676
|
+
getDeviceAuthConfig,
|
|
15677
|
+
getDeviceAuthInfoService,
|
|
14744
15678
|
getDummyPasswordHash,
|
|
14745
15679
|
getEnabledOAuthProviders,
|
|
14746
15680
|
getGoogleAccessToken,
|
|
@@ -14752,6 +15686,7 @@ export {
|
|
|
14752
15686
|
getKeyId,
|
|
14753
15687
|
getKeySize,
|
|
14754
15688
|
getLocale,
|
|
15689
|
+
getMachinePrincipal,
|
|
14755
15690
|
getOAuthProvider,
|
|
14756
15691
|
getOneTimeTokenManager,
|
|
14757
15692
|
getOpsToken,
|
|
@@ -14778,6 +15713,7 @@ export {
|
|
|
14778
15713
|
hasAnyRole,
|
|
14779
15714
|
hasPermission,
|
|
14780
15715
|
hasRole,
|
|
15716
|
+
hashDeviceCode,
|
|
14781
15717
|
hashPassword,
|
|
14782
15718
|
initOneTimeTokenManager,
|
|
14783
15719
|
initializeAuth,
|
|
@@ -14797,11 +15733,14 @@ export {
|
|
|
14797
15733
|
listOpsTokensService,
|
|
14798
15734
|
loginService,
|
|
14799
15735
|
logoutService,
|
|
15736
|
+
machineAuth,
|
|
14800
15737
|
matchOAuthCsrfCookies,
|
|
15738
|
+
matchesCsrfToken,
|
|
14801
15739
|
naverProvider,
|
|
14802
15740
|
normalizeEmail,
|
|
14803
15741
|
normalizeOptionalEmail,
|
|
14804
15742
|
normalizeStoredEmails,
|
|
15743
|
+
normalizeUserCode,
|
|
14805
15744
|
oauthCallbackService,
|
|
14806
15745
|
oauthNativeService,
|
|
14807
15746
|
oauthStartService,
|
|
@@ -14815,9 +15754,11 @@ export {
|
|
|
14815
15754
|
parseDuration,
|
|
14816
15755
|
permissions,
|
|
14817
15756
|
permissionsRepository,
|
|
15757
|
+
pollDeviceAuthService,
|
|
14818
15758
|
purgeUserService,
|
|
14819
15759
|
refreshAccessToken,
|
|
14820
15760
|
registerAuthProfile,
|
|
15761
|
+
registerMachineVerifier,
|
|
14821
15762
|
registerOAuthProvider,
|
|
14822
15763
|
registerPublicKeyService,
|
|
14823
15764
|
registerService,
|
|
@@ -14826,6 +15767,7 @@ export {
|
|
|
14826
15767
|
requestSignupLinkService,
|
|
14827
15768
|
requireAnyPermission,
|
|
14828
15769
|
requireEnabledProvider,
|
|
15770
|
+
requireMachineScope,
|
|
14829
15771
|
requireOpsScope,
|
|
14830
15772
|
requirePermissions,
|
|
14831
15773
|
requireRole,
|
|
@@ -14851,6 +15793,7 @@ export {
|
|
|
14851
15793
|
signupLinkTokens,
|
|
14852
15794
|
signupLinkTokensRepository,
|
|
14853
15795
|
socialAccountsRepository,
|
|
15796
|
+
startDeviceAuthService,
|
|
14854
15797
|
sweepDuePurges,
|
|
14855
15798
|
unsealSession,
|
|
14856
15799
|
updateLastLoginService,
|