@spfn/auth 0.2.0-beta.1 → 0.2.0-beta.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @spfn/auth - Technical Documentation
2
2
 
3
- **Version:** 0.1.0-alpha.88
3
+ **Version:** 0.2.0-beta.11
4
4
  **Status:** Alpha - Internal Development
5
5
 
6
6
  > **Note:** This is a technical documentation for developers working on the @spfn/auth package.
@@ -12,11 +12,11 @@
12
12
 
13
13
  - [Overview](#overview)
14
14
  - [Installation](#installation)
15
+ - [Admin Account Setup](#6-admin-account-setup)
15
16
  - [Architecture](#architecture)
16
17
  - [Package Structure](#package-structure)
17
18
  - [Module Exports](#module-exports)
18
19
  - [Email & SMS Services](#email--sms-services)
19
- - [Email Templates](#email-templates)
20
20
  - [Server-Side API](#server-side-api)
21
21
  - [Database Schema](#database-schema)
22
22
  - [RBAC System](#rbac-system)
@@ -90,18 +90,26 @@ export const appRouter = defineRouter({
90
90
 
91
91
  ### 3. Configure Client (Next.js)
92
92
 
93
- #### Register Router Metadata and Errors in `api-client.ts`
93
+ #### Option A: Use the built-in `authApi` (Recommended)
94
+
95
+ ```typescript
96
+ import { authApi } from '@spfn/auth';
97
+
98
+ // Type-safe API calls for auth routes
99
+ const session = await authApi.getAuthSession.call({});
100
+ ```
101
+
102
+ #### Option B: Register Error Registry in Custom API Client
94
103
 
95
104
  ```typescript
96
105
  import { createApi } from '@spfn/core/nextjs';
97
106
  import type { AppRouter } from '@/server/router';
98
- import { appMetadata as authAppMetadata } from "@spfn/auth";
99
107
  import { authErrorRegistry } from "@spfn/auth/errors";
100
108
  import { appMetadata } from '@/server/router.metadata';
101
109
  import { errorRegistry } from "@spfn/core/errors";
102
110
 
103
111
  export const api = createApi<AppRouter>({
104
- metadata: { ...appMetadata, ...authAppMetadata },
112
+ metadata: appMetadata,
105
113
  errorRegistry: errorRegistry.concat(authErrorRegistry),
106
114
  });
107
115
  ```
@@ -144,6 +152,83 @@ pnpm spfn db generate
144
152
  pnpm spfn db migrate
145
153
  ```
146
154
 
155
+ ### 6. Admin Account Setup
156
+
157
+ Admin accounts are automatically created on server startup via `createAuthLifecycle()`.
158
+ Choose one of the following methods:
159
+
160
+ #### Method 1: JSON Format (Recommended)
161
+
162
+ Best for multiple accounts with full configuration:
163
+
164
+ ```bash
165
+ SPFN_AUTH_ADMIN_ACCOUNTS='[
166
+ {"email": "superadmin@example.com", "password": "secure-pass-1", "role": "superadmin"},
167
+ {"email": "admin@example.com", "password": "secure-pass-2", "role": "admin"},
168
+ {"email": "manager@example.com", "password": "secure-pass-3", "role": "user"}
169
+ ]'
170
+ ```
171
+
172
+ **JSON Schema:**
173
+ ```typescript
174
+ interface AdminAccountConfig {
175
+ email: string; // Required
176
+ password: string; // Required
177
+ role?: string; // Default: 'user' (options: 'user', 'admin', 'superadmin')
178
+ phone?: string; // Optional
179
+ passwordChangeRequired?: boolean; // Default: true
180
+ }
181
+ ```
182
+
183
+ #### Method 2: CSV Format
184
+
185
+ For multiple accounts with simpler configuration:
186
+
187
+ ```bash
188
+ SPFN_AUTH_ADMIN_EMAILS=admin@example.com,manager@example.com
189
+ SPFN_AUTH_ADMIN_PASSWORDS=admin-pass,manager-pass
190
+ SPFN_AUTH_ADMIN_ROLES=superadmin,admin
191
+ ```
192
+
193
+ #### Method 3: Single Account (Legacy)
194
+
195
+ Simplest format for a single superadmin:
196
+
197
+ ```bash
198
+ SPFN_AUTH_ADMIN_EMAIL=admin@example.com
199
+ SPFN_AUTH_ADMIN_PASSWORD=secure-password
200
+ ```
201
+
202
+ > **Note:** This method always creates a `superadmin` role account.
203
+
204
+ #### Default Behavior
205
+
206
+ All admin accounts created via environment variables have:
207
+ - `emailVerifiedAt`: Auto-verified (current timestamp)
208
+ - `passwordChangeRequired`: `true` (must change on first login)
209
+ - `status`: `active`
210
+
211
+ #### Programmatic Creation
212
+
213
+ You can also create admin accounts programmatically:
214
+
215
+ ```typescript
216
+ import { usersRepository, getRoleByName, hashPassword } from '@spfn/auth/server';
217
+
218
+ // After initializeAuth() has been called
219
+ const role = await getRoleByName('admin');
220
+ const passwordHash = await hashPassword('secure-password');
221
+
222
+ await usersRepository.create({
223
+ email: 'admin@example.com',
224
+ passwordHash,
225
+ roleId: role.id,
226
+ emailVerifiedAt: new Date(),
227
+ passwordChangeRequired: true,
228
+ status: 'active',
229
+ });
230
+ ```
231
+
147
232
  ---
148
233
 
149
234
  ## Architecture
@@ -335,20 +420,15 @@ packages/auth/
335
420
 
336
421
  ### Common Module (`@spfn/auth`)
337
422
 
338
- **Entities:**
423
+ **API Client:**
339
424
  ```typescript
340
- import {
341
- users,
342
- userPublicKeys,
343
- verificationCodes,
344
- roles,
345
- permissions,
346
- rolePermissions,
347
- userPermissions,
348
- userInvitations,
349
- userSocialAccounts,
350
- userProfiles
351
- } from '@spfn/auth';
425
+ import { authApi } from '@spfn/auth';
426
+
427
+ // Type-safe API calls
428
+ const session = await authApi.getAuthSession.call({});
429
+ const result = await authApi.login.call({
430
+ body: { email, password, fingerprint, publicKey, keyId }
431
+ });
352
432
  ```
353
433
 
354
434
  **Types:**
@@ -359,6 +439,9 @@ import type {
359
439
  VerificationCode,
360
440
  Role,
361
441
  Permission,
442
+ AuthSession,
443
+ UserProfile,
444
+ ProfileInfo,
362
445
  // ... etc
363
446
  } from '@spfn/auth';
364
447
  ```
@@ -380,6 +463,34 @@ import type {
380
463
  } from '@spfn/auth';
381
464
  ```
382
465
 
466
+ **Validation Patterns:**
467
+ ```typescript
468
+ import {
469
+ UUID_PATTERN,
470
+ EMAIL_PATTERN,
471
+ BASE64_PATTERN,
472
+ FINGERPRINT_PATTERN,
473
+ PHONE_PATTERN,
474
+ } from '@spfn/auth';
475
+ ```
476
+
477
+ **Route Map (for RPC Proxy):**
478
+ ```typescript
479
+ import { authRouteMap } from '@spfn/auth';
480
+
481
+ // Use in Next.js RPC proxy (app/api/rpc/[routeName]/route.ts)
482
+ import '@spfn/auth/nextjs/api'; // Auto-register auth interceptors
483
+ import { routeMap } from '@/generated/route-map';
484
+ import { authRouteMap } from '@spfn/auth';
485
+ import { createRpcProxy } from '@spfn/core/nextjs/proxy';
486
+
487
+ export const { GET, POST } = createRpcProxy({
488
+ routeMap: { ...routeMap, ...authRouteMap }
489
+ });
490
+ ```
491
+
492
+ > **Note:** Database entities (`users`, `userPublicKeys`, etc.) are exported from `@spfn/auth/server`, not the common module.
493
+
383
494
  ---
384
495
 
385
496
  ### Server Module (`@spfn/auth/server`)
@@ -456,22 +567,10 @@ import {
456
567
 
457
568
  // Session
458
569
  getAuthSessionService,
459
- getUserProfileService,
460
570
 
461
- // Email
462
- sendEmail,
463
- registerEmailProvider,
464
-
465
- // SMS
466
- sendSMS,
467
- registerSMSProvider,
468
-
469
- // Email Templates
470
- registerEmailTemplates,
471
- getVerificationCodeTemplate,
472
- getWelcomeTemplate,
473
- getPasswordResetTemplate,
474
- getInvitationTemplate,
571
+ // User Profile
572
+ getUserProfileService,
573
+ updateUserProfileService,
475
574
  } from '@spfn/auth/server';
476
575
  ```
477
576
 
@@ -495,10 +594,11 @@ import {
495
594
  import {
496
595
  authenticate,
497
596
  requirePermissions,
597
+ requireAnyPermission,
498
598
  requireRole,
499
599
  } from '@spfn/auth/server';
500
600
 
501
- // Usage
601
+ // Usage - all permissions required
502
602
  app.bind(
503
603
  myContract,
504
604
  [authenticate, requirePermissions('user:delete')],
@@ -506,6 +606,15 @@ app.bind(
506
606
  // Handler
507
607
  }
508
608
  );
609
+
610
+ // Usage - any of the permissions
611
+ app.bind(
612
+ myContract,
613
+ [authenticate, requireAnyPermission('content:read', 'admin:access')],
614
+ async (c) => {
615
+ // User has either content:read OR admin:access
616
+ }
617
+ );
509
618
  ```
510
619
 
511
620
  **Helpers:**
@@ -679,141 +788,24 @@ export default async function DashboardPage()
679
788
 
680
789
  ## Email & SMS Services
681
790
 
682
- ### Email Service
683
-
684
- The email service uses AWS SES by default, with fallback to console logging in development.
685
-
686
- **Send Email:**
687
- ```typescript
688
- import { sendEmail } from '@spfn/auth/server';
689
-
690
- await sendEmail({
691
- to: 'user@example.com',
692
- subject: 'Welcome!',
693
- text: 'Plain text content',
694
- html: '<h1>HTML content</h1>',
695
- purpose: 'welcome', // for logging
696
- });
697
- ```
698
-
699
- **Custom Email Provider:**
700
- ```typescript
701
- import { registerEmailProvider } from '@spfn/auth/server';
702
-
703
- // Register SendGrid provider
704
- registerEmailProvider({
705
- name: 'sendgrid',
706
- sendEmail: async ({ to, subject, text, html }) => {
707
- // Your SendGrid implementation
708
- return { success: true, messageId: '...' };
709
- },
710
- });
711
- ```
712
-
713
- ---
714
-
715
- ### SMS Service
716
-
717
- The SMS service uses AWS SNS by default.
718
-
719
- **Send SMS:**
720
- ```typescript
721
- import { sendSMS } from '@spfn/auth/server';
722
-
723
- await sendSMS({
724
- phone: '+821012345678', // E.164 format
725
- message: 'Your code is: 123456',
726
- purpose: 'verification',
727
- });
728
- ```
791
+ > **⚠️ DEPRECATED:** Email and SMS functionality has been moved to `@spfn/notification` package.
729
792
 
730
- **Custom SMS Provider:**
731
- ```typescript
732
- import { registerSMSProvider } from '@spfn/auth/server';
733
-
734
- // Register Twilio provider
735
- registerSMSProvider({
736
- name: 'twilio',
737
- sendSMS: async ({ phone, message }) => {
738
- // Your Twilio implementation
739
- return { success: true, messageId: '...' };
740
- },
741
- });
742
- ```
793
+ ### Migration Guide
743
794
 
744
- ---
745
-
746
- ## Email Templates
747
-
748
- ### Built-in Templates
749
-
750
- | Template | Function | Purpose |
751
- |----------|----------|---------|
752
- | `verificationCode` | `getVerificationCodeTemplate` | Verification codes (registration, login, password reset) |
753
- | `welcome` | `getWelcomeTemplate` | Welcome email after registration |
754
- | `passwordReset` | `getPasswordResetTemplate` | Password reset link |
755
- | `invitation` | `getInvitationTemplate` | User invitation |
756
-
757
- **Usage:**
758
795
  ```typescript
759
- import { getVerificationCodeTemplate, sendEmail } from '@spfn/auth/server';
760
-
761
- const { subject, text, html } = getVerificationCodeTemplate({
762
- code: '123456',
763
- purpose: 'registration',
764
- expiresInMinutes: 5,
765
- appName: 'MyApp',
766
- });
796
+ // Before (deprecated)
797
+ import { sendEmail, sendSMS } from '@spfn/auth/server';
767
798
 
768
- await sendEmail({ to: 'user@example.com', subject, text, html });
799
+ // After (recommended)
800
+ import { sendEmail, sendSMS } from '@spfn/notification/server';
769
801
  ```
770
802
 
771
- ---
772
-
773
- ### Custom Templates
774
-
775
- Register custom templates to override defaults with your brand design:
776
-
777
- ```typescript
778
- import { registerEmailTemplates } from '@spfn/auth/server';
779
-
780
- // Register at app initialization (e.g., server.config.ts)
781
- registerEmailTemplates({
782
- // Override verification code template
783
- verificationCode: ({ code, purpose, expiresInMinutes, appName }) => ({
784
- subject: `[${appName}] Your verification code`,
785
- text: `Your code: ${code}\nExpires in ${expiresInMinutes} minutes.`,
786
- html: `
787
- <div style="font-family: Arial, sans-serif;">
788
- <img src="https://myapp.com/logo.png" alt="Logo" />
789
- <h1>Verification Code</h1>
790
- <div style="font-size: 32px; font-weight: bold;">${code}</div>
791
- <p>This code expires in ${expiresInMinutes} minutes.</p>
792
- </div>
793
- `,
794
- }),
795
-
796
- // Override invitation template
797
- invitation: ({ inviteLink, inviterName, roleName, appName }) => ({
798
- subject: `${inviterName} invited you to ${appName}`,
799
- text: `Accept invitation: ${inviteLink}`,
800
- html: `
801
- <h1>You're Invited!</h1>
802
- <p>${inviterName} invited you to join ${appName} as ${roleName}.</p>
803
- <a href="${inviteLink}">Accept Invitation</a>
804
- `,
805
- }),
806
- });
807
- ```
803
+ The `@spfn/notification` package provides:
804
+ - Multi-channel support (Email, SMS, Slack, Push)
805
+ - Template system with variable substitution
806
+ - Multiple provider support (AWS SES, SNS, SendGrid, Twilio, etc.)
808
807
 
809
- **Template Parameters:**
810
-
811
- | Template | Parameters |
812
- |----------|------------|
813
- | `verificationCode` | `code`, `purpose`, `expiresInMinutes?`, `appName?` |
814
- | `welcome` | `email`, `appName?` |
815
- | `passwordReset` | `resetLink`, `expiresInMinutes?`, `appName?` |
816
- | `invitation` | `inviteLink`, `inviterName?`, `roleName?`, `appName?` |
808
+ For documentation, see `@spfn/notification` package README.
817
809
 
818
810
  ---
819
811
 
@@ -1167,7 +1159,7 @@ CREATE TABLE permissions (
1167
1159
 
1168
1160
  **Built-in Permissions:**
1169
1161
  - `auth:self:manage`
1170
- - `user:read`, `user:write`, `user:delete`
1162
+ - `user:read`, `user:write`, `user:delete`, `user:invite`
1171
1163
  - `rbac:role:manage`, `rbac:permission:manage`
1172
1164
 
1173
1165
  ---
@@ -1327,7 +1319,7 @@ await initializeAuth({
1327
1319
 
1328
1320
  **Permissions:**
1329
1321
  - `auth:self:manage` - Change password, rotate keys
1330
- - `user:read`, `user:write`, `user:delete`
1322
+ - `user:read`, `user:write`, `user:delete`, `user:invite`
1331
1323
  - `rbac:role:manage`, `rbac:permission:manage`
1332
1324
 
1333
1325
  ---
@@ -1335,7 +1327,7 @@ await initializeAuth({
1335
1327
  ### Middleware Usage
1336
1328
 
1337
1329
  ```typescript
1338
- import { authenticate, requirePermissions, requireRole } from '@spfn/auth/server';
1330
+ import { authenticate, requirePermissions, requireAnyPermission, requireRole } from '@spfn/auth/server';
1339
1331
 
1340
1332
  // Single permission
1341
1333
  app.bind(
@@ -1355,6 +1347,15 @@ app.bind(
1355
1347
  }
1356
1348
  );
1357
1349
 
1350
+ // Any of the permissions (at least one required)
1351
+ app.bind(
1352
+ viewContentContract,
1353
+ [authenticate, requireAnyPermission('content:read', 'admin:access')],
1354
+ async (c) => {
1355
+ // User has either content:read OR admin:access
1356
+ }
1357
+ );
1358
+
1358
1359
  // Role-based
1359
1360
  app.bind(
1360
1361
  adminDashboardContract,
@@ -1957,6 +1958,6 @@ MIT License - See LICENSE file for details.
1957
1958
 
1958
1959
  ---
1959
1960
 
1960
- **Last Updated:** 2025-12-07
1961
- **Document Version:** 2.2.0 (Technical Documentation)
1962
- **Package Version:** 0.1.0-alpha.88
1961
+ **Last Updated:** 2026-01-25
1962
+ **Document Version:** 2.3.0 (Technical Documentation)
1963
+ **Package Version:** 0.2.0-beta.11