@originals/auth 1.8.1 → 1.8.3
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/package.json +8 -12
- package/.turbo/turbo-build.log +0 -1
- package/eslint.config.js +0 -32
- package/src/client/index.ts +0 -36
- package/src/client/server-auth.ts +0 -101
- package/src/client/turnkey-client.ts +0 -364
- package/src/client/turnkey-did-signer.ts +0 -203
- package/src/index.ts +0 -32
- package/src/server/email-auth.ts +0 -258
- package/src/server/index.ts +0 -42
- package/src/server/jwt.ts +0 -154
- package/src/server/middleware.ts +0 -142
- package/src/server/turnkey-client.ts +0 -156
- package/src/server/turnkey-signer.ts +0 -170
- package/src/types.ts +0 -172
- package/tests/index.test.ts +0 -29
- package/tests/server-auth.test.ts +0 -167
- package/tsconfig.json +0 -32
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Turnkey DID Signer Adapter
|
|
3
|
-
* Adapts Turnkey signing to work with didwebvh-ts signer interface
|
|
4
|
-
* Uses @turnkey/sdk-server for all Turnkey operations (no viem/ethers dependency)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { Turnkey } from '@turnkey/sdk-server';
|
|
8
|
-
import { OriginalsSDK, encoding } from '@originals/sdk';
|
|
9
|
-
import type { TurnkeyWalletAccount } from '../types';
|
|
10
|
-
import { TurnkeySessionExpiredError, withTokenExpiration } from './turnkey-client';
|
|
11
|
-
|
|
12
|
-
interface SigningInput {
|
|
13
|
-
document: Record<string, unknown>;
|
|
14
|
-
proof: Record<string, unknown>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface SigningOutput {
|
|
18
|
-
proofValue: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Signer that uses Turnkey for signing DID documents
|
|
23
|
-
* Compatible with didwebvh-ts signer interface
|
|
24
|
-
*/
|
|
25
|
-
export class TurnkeyDIDSigner {
|
|
26
|
-
private turnkeyClient: Turnkey;
|
|
27
|
-
private signWith: string;
|
|
28
|
-
private subOrgId: string;
|
|
29
|
-
private publicKeyMultibase: string;
|
|
30
|
-
private onExpired?: () => void;
|
|
31
|
-
|
|
32
|
-
constructor(
|
|
33
|
-
turnkeyClient: Turnkey,
|
|
34
|
-
signWith: string,
|
|
35
|
-
subOrgId: string,
|
|
36
|
-
publicKeyMultibase: string,
|
|
37
|
-
onExpired?: () => void
|
|
38
|
-
) {
|
|
39
|
-
this.turnkeyClient = turnkeyClient;
|
|
40
|
-
this.signWith = signWith;
|
|
41
|
-
this.subOrgId = subOrgId;
|
|
42
|
-
this.publicKeyMultibase = publicKeyMultibase;
|
|
43
|
-
this.onExpired = onExpired;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Sign the document and proof using Turnkey
|
|
48
|
-
*/
|
|
49
|
-
async sign(input: SigningInput): Promise<SigningOutput> {
|
|
50
|
-
return withTokenExpiration(async () => {
|
|
51
|
-
try {
|
|
52
|
-
// Use SDK's prepareDIDDataForSigning
|
|
53
|
-
const dataToSign = await OriginalsSDK.prepareDIDDataForSigning(input.document, input.proof);
|
|
54
|
-
|
|
55
|
-
// Sign with Turnkey via server SDK
|
|
56
|
-
const result = await this.turnkeyClient.apiClient().signRawPayload({
|
|
57
|
-
organizationId: this.subOrgId,
|
|
58
|
-
signWith: this.signWith,
|
|
59
|
-
payload: Buffer.from(dataToSign).toString('hex'),
|
|
60
|
-
encoding: 'PAYLOAD_ENCODING_HEXADECIMAL',
|
|
61
|
-
hashFunction: 'HASH_FUNCTION_NO_OP',
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const r = result.r;
|
|
65
|
-
const s = result.s;
|
|
66
|
-
|
|
67
|
-
if (!r || !s) {
|
|
68
|
-
throw new Error('Invalid signature response from Turnkey');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// For Ed25519, combine r+s only (64 bytes total)
|
|
72
|
-
const cleanR = r.startsWith('0x') ? r.slice(2) : r;
|
|
73
|
-
const cleanS = s.startsWith('0x') ? s.slice(2) : s;
|
|
74
|
-
const combinedHex = cleanR + cleanS;
|
|
75
|
-
|
|
76
|
-
const signatureBytes = Buffer.from(combinedHex, 'hex');
|
|
77
|
-
|
|
78
|
-
if (signatureBytes.length !== 64) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`Invalid Ed25519 signature length: ${signatureBytes.length} (expected 64 bytes)`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const proofValue = encoding.multibase.encode(signatureBytes, 'base58btc');
|
|
85
|
-
|
|
86
|
-
return { proofValue };
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.error('[TurnkeyDIDSigner] Error signing with Turnkey:', error);
|
|
89
|
-
|
|
90
|
-
const errorStr = JSON.stringify(error);
|
|
91
|
-
if (
|
|
92
|
-
errorStr.toLowerCase().includes('api_key_expired') ||
|
|
93
|
-
errorStr.toLowerCase().includes('expired api key') ||
|
|
94
|
-
errorStr.toLowerCase().includes('"code":16')
|
|
95
|
-
) {
|
|
96
|
-
console.warn('Detected expired API key in sign method, calling onExpired');
|
|
97
|
-
if (this.onExpired) {
|
|
98
|
-
this.onExpired();
|
|
99
|
-
}
|
|
100
|
-
throw new TurnkeySessionExpiredError();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
throw error;
|
|
104
|
-
}
|
|
105
|
-
}, this.onExpired);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Get the verification method ID for this signer
|
|
110
|
-
*/
|
|
111
|
-
getVerificationMethodId(): string {
|
|
112
|
-
return `did:key:${this.publicKeyMultibase}`;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Verify a signature
|
|
117
|
-
*/
|
|
118
|
-
async verify(
|
|
119
|
-
signature: Uint8Array,
|
|
120
|
-
message: Uint8Array,
|
|
121
|
-
publicKey: Uint8Array
|
|
122
|
-
): Promise<boolean> {
|
|
123
|
-
try {
|
|
124
|
-
return await OriginalsSDK.verifyDIDSignature(signature, message, publicKey);
|
|
125
|
-
} catch (error) {
|
|
126
|
-
console.error('[TurnkeyDIDSigner] Error verifying signature:', error);
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Create a DID:WebVH using OriginalsSDK.createDIDOriginal() with Turnkey signing
|
|
134
|
-
*/
|
|
135
|
-
export async function createDIDWithTurnkey(params: {
|
|
136
|
-
turnkeyClient: Turnkey;
|
|
137
|
-
updateKeyAccount: TurnkeyWalletAccount;
|
|
138
|
-
subOrgId: string;
|
|
139
|
-
authKeyPublic: string;
|
|
140
|
-
assertionKeyPublic: string;
|
|
141
|
-
updateKeyPublic: string;
|
|
142
|
-
domain: string;
|
|
143
|
-
slug: string;
|
|
144
|
-
onExpired?: () => void;
|
|
145
|
-
}): Promise<{
|
|
146
|
-
did: string;
|
|
147
|
-
didDocument: unknown;
|
|
148
|
-
didLog: unknown;
|
|
149
|
-
}> {
|
|
150
|
-
const {
|
|
151
|
-
turnkeyClient,
|
|
152
|
-
updateKeyAccount,
|
|
153
|
-
subOrgId,
|
|
154
|
-
authKeyPublic,
|
|
155
|
-
assertionKeyPublic,
|
|
156
|
-
updateKeyPublic,
|
|
157
|
-
domain,
|
|
158
|
-
slug,
|
|
159
|
-
onExpired,
|
|
160
|
-
} = params;
|
|
161
|
-
|
|
162
|
-
// Create Turnkey signer for the update key
|
|
163
|
-
const signer = new TurnkeyDIDSigner(
|
|
164
|
-
turnkeyClient,
|
|
165
|
-
updateKeyAccount.address,
|
|
166
|
-
subOrgId,
|
|
167
|
-
updateKeyPublic,
|
|
168
|
-
onExpired
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
// Use SDK's createDIDOriginal
|
|
172
|
-
const result = await OriginalsSDK.createDIDOriginal({
|
|
173
|
-
type: 'did',
|
|
174
|
-
domain,
|
|
175
|
-
signer,
|
|
176
|
-
verifier: signer,
|
|
177
|
-
updateKeys: [signer.getVerificationMethodId()],
|
|
178
|
-
verificationMethods: [
|
|
179
|
-
{
|
|
180
|
-
id: '#key-0',
|
|
181
|
-
type: 'Multikey',
|
|
182
|
-
controller: '',
|
|
183
|
-
publicKeyMultibase: authKeyPublic,
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
id: '#key-1',
|
|
187
|
-
type: 'Multikey',
|
|
188
|
-
controller: '',
|
|
189
|
-
publicKeyMultibase: assertionKeyPublic,
|
|
190
|
-
},
|
|
191
|
-
],
|
|
192
|
-
paths: [slug],
|
|
193
|
-
portable: false,
|
|
194
|
-
authentication: ['#key-0'],
|
|
195
|
-
assertionMethod: ['#key-1'],
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
did: result.did,
|
|
200
|
-
didDocument: result.doc,
|
|
201
|
-
didLog: result.log,
|
|
202
|
-
};
|
|
203
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @originals/auth - Turnkey-based authentication for the Originals Protocol
|
|
3
|
-
*
|
|
4
|
-
* This package provides authentication utilities for both server and client applications.
|
|
5
|
-
*
|
|
6
|
-
* Server-side:
|
|
7
|
-
* ```typescript
|
|
8
|
-
* import { createAuthMiddleware, initiateEmailAuth, verifyEmailAuth } from '@originals/auth/server';
|
|
9
|
-
* ```
|
|
10
|
-
*
|
|
11
|
-
* Client-side (pure functions, no React):
|
|
12
|
-
* ```typescript
|
|
13
|
-
* import { initializeTurnkeyClient, initOtp, completeOtp, fetchWallets } from '@originals/auth/client';
|
|
14
|
-
* ```
|
|
15
|
-
*
|
|
16
|
-
* Types:
|
|
17
|
-
* ```typescript
|
|
18
|
-
* import type { AuthUser, TokenPayload, TurnkeyWallet } from '@originals/auth/types';
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
// Re-export types
|
|
23
|
-
export * from './types';
|
|
24
|
-
|
|
25
|
-
// Re-export server utilities (for convenience, though subpath is preferred)
|
|
26
|
-
export * from './server';
|
|
27
|
-
|
|
28
|
-
// Note: Client utilities should be imported from '@originals/auth/client'
|
|
29
|
-
// to avoid bundling React in server environments
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
package/src/server/email-auth.ts
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Turnkey Email Authentication Service
|
|
3
|
-
* Implements email-based authentication using Turnkey's OTP flow
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Turnkey } from '@turnkey/sdk-server';
|
|
7
|
-
import { sha256 } from '@noble/hashes/sha2.js';
|
|
8
|
-
import { bytesToHex } from '@noble/hashes/utils.js';
|
|
9
|
-
import type { EmailAuthSession, InitiateAuthResult, VerifyAuthResult } from '../types';
|
|
10
|
-
import { getOrCreateTurnkeySubOrg } from './turnkey-client';
|
|
11
|
-
|
|
12
|
-
// Session timeout (15 minutes to match Turnkey OTP)
|
|
13
|
-
const SESSION_TIMEOUT = 15 * 60 * 1000;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Session storage interface for pluggable session management
|
|
17
|
-
*/
|
|
18
|
-
export interface SessionStorage {
|
|
19
|
-
get(sessionId: string): EmailAuthSession | undefined;
|
|
20
|
-
set(sessionId: string, session: EmailAuthSession): void;
|
|
21
|
-
delete(sessionId: string): void;
|
|
22
|
-
cleanup(): void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Create an in-memory session storage
|
|
27
|
-
* For production, consider using Redis or a database
|
|
28
|
-
*/
|
|
29
|
-
export function createInMemorySessionStorage(): SessionStorage {
|
|
30
|
-
const sessions = new Map<string, EmailAuthSession>();
|
|
31
|
-
|
|
32
|
-
// Start cleanup interval
|
|
33
|
-
const cleanupInterval = setInterval(() => {
|
|
34
|
-
const now = Date.now();
|
|
35
|
-
for (const [sessionId, session] of sessions.entries()) {
|
|
36
|
-
if (now - session.timestamp > SESSION_TIMEOUT) {
|
|
37
|
-
sessions.delete(sessionId);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}, 60 * 1000);
|
|
41
|
-
|
|
42
|
-
// Keep the interval from preventing process exit
|
|
43
|
-
if (cleanupInterval.unref) {
|
|
44
|
-
cleanupInterval.unref();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {
|
|
48
|
-
get: (sessionId: string) => sessions.get(sessionId),
|
|
49
|
-
set: (sessionId: string, session: EmailAuthSession) => sessions.set(sessionId, session),
|
|
50
|
-
delete: (sessionId: string) => sessions.delete(sessionId),
|
|
51
|
-
cleanup: () => {
|
|
52
|
-
clearInterval(cleanupInterval);
|
|
53
|
-
sessions.clear();
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Default session storage
|
|
59
|
-
let defaultSessionStorage: SessionStorage | null = null;
|
|
60
|
-
|
|
61
|
-
function getDefaultSessionStorage(): SessionStorage {
|
|
62
|
-
if (!defaultSessionStorage) {
|
|
63
|
-
defaultSessionStorage = createInMemorySessionStorage();
|
|
64
|
-
}
|
|
65
|
-
return defaultSessionStorage;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Generate a random session ID
|
|
70
|
-
*/
|
|
71
|
-
function generateSessionId(): string {
|
|
72
|
-
return `session_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Initiate email authentication using Turnkey OTP
|
|
77
|
-
* Sends a 6-digit OTP code to the user's email
|
|
78
|
-
*/
|
|
79
|
-
export async function initiateEmailAuth(
|
|
80
|
-
email: string,
|
|
81
|
-
turnkeyClient: Turnkey,
|
|
82
|
-
sessionStorage?: SessionStorage
|
|
83
|
-
): Promise<InitiateAuthResult> {
|
|
84
|
-
const storage = sessionStorage ?? getDefaultSessionStorage();
|
|
85
|
-
|
|
86
|
-
// Validate email format
|
|
87
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
88
|
-
if (!emailRegex.test(email)) {
|
|
89
|
-
throw new Error('Invalid email format');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
console.log(`\n🚀 Initiating email auth for: ${email}`);
|
|
93
|
-
|
|
94
|
-
// Step 1: Get or create Turnkey sub-organization
|
|
95
|
-
const subOrgId = await getOrCreateTurnkeySubOrg(email, turnkeyClient);
|
|
96
|
-
|
|
97
|
-
// Step 2: Send OTP via Turnkey
|
|
98
|
-
console.log(`📨 Sending OTP to ${email} via Turnkey...`);
|
|
99
|
-
|
|
100
|
-
// Generate a unique user identifier for rate limiting
|
|
101
|
-
const data = new TextEncoder().encode(email);
|
|
102
|
-
const hash = sha256(data);
|
|
103
|
-
const userIdentifier = bytesToHex(hash);
|
|
104
|
-
|
|
105
|
-
const otpResult = await turnkeyClient.apiClient().initOtp({
|
|
106
|
-
otpType: 'OTP_TYPE_EMAIL',
|
|
107
|
-
contact: email,
|
|
108
|
-
userIdentifier: userIdentifier,
|
|
109
|
-
appName: 'Originals',
|
|
110
|
-
otpLength: 6,
|
|
111
|
-
alphanumeric: false,
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
const otpId = otpResult.otpId;
|
|
115
|
-
|
|
116
|
-
if (!otpId) {
|
|
117
|
-
throw new Error('Failed to initiate OTP - no OTP ID returned');
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
console.log(`✅ OTP sent! OTP ID: ${otpId}`);
|
|
121
|
-
|
|
122
|
-
// Create auth session
|
|
123
|
-
const sessionId = generateSessionId();
|
|
124
|
-
storage.set(sessionId, {
|
|
125
|
-
email,
|
|
126
|
-
subOrgId,
|
|
127
|
-
otpId,
|
|
128
|
-
timestamp: Date.now(),
|
|
129
|
-
verified: false,
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
console.log('='.repeat(60));
|
|
133
|
-
console.log(`📧 Check ${email} for the verification code!`);
|
|
134
|
-
console.log(` Session ID: ${sessionId}`);
|
|
135
|
-
console.log(` Valid for: 15 minutes`);
|
|
136
|
-
console.log('='.repeat(60) + '\n');
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
sessionId,
|
|
140
|
-
message: 'Verification code sent to your email. Check your inbox!',
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Verify email authentication code using Turnkey OTP
|
|
146
|
-
*/
|
|
147
|
-
export async function verifyEmailAuth(
|
|
148
|
-
sessionId: string,
|
|
149
|
-
code: string,
|
|
150
|
-
turnkeyClient: Turnkey,
|
|
151
|
-
sessionStorage?: SessionStorage
|
|
152
|
-
): Promise<VerifyAuthResult> {
|
|
153
|
-
const storage = sessionStorage ?? getDefaultSessionStorage();
|
|
154
|
-
const session = storage.get(sessionId);
|
|
155
|
-
|
|
156
|
-
if (!session) {
|
|
157
|
-
throw new Error('Invalid or expired session');
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Check if session has expired
|
|
161
|
-
if (Date.now() - session.timestamp > SESSION_TIMEOUT) {
|
|
162
|
-
storage.delete(sessionId);
|
|
163
|
-
throw new Error('Session expired. Please request a new code.');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!session.otpId) {
|
|
167
|
-
throw new Error('OTP ID not found in session');
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (!session.subOrgId) {
|
|
171
|
-
throw new Error('Sub-organization ID not found');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
console.log(`\n🔐 Verifying OTP for session ${sessionId}...`);
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
// Verify the OTP code with Turnkey
|
|
178
|
-
const verifyResult = await turnkeyClient.apiClient().verifyOtp({
|
|
179
|
-
otpId: session.otpId,
|
|
180
|
-
otpCode: code,
|
|
181
|
-
expirationSeconds: '900', // 15 minutes
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
if (!verifyResult.verificationToken) {
|
|
185
|
-
throw new Error('OTP verification failed - no verification token returned');
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
console.log(`✅ OTP verified successfully!`);
|
|
189
|
-
|
|
190
|
-
// Mark session as verified
|
|
191
|
-
session.verified = true;
|
|
192
|
-
storage.set(sessionId, session);
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
verified: true,
|
|
196
|
-
email: session.email,
|
|
197
|
-
subOrgId: session.subOrgId,
|
|
198
|
-
};
|
|
199
|
-
} catch (error) {
|
|
200
|
-
console.error('❌ OTP verification failed:', error);
|
|
201
|
-
throw new Error(
|
|
202
|
-
`Invalid verification code: ${error instanceof Error ? error.message : String(error)}`
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Check if a session is verified
|
|
209
|
-
*/
|
|
210
|
-
export function isSessionVerified(
|
|
211
|
-
sessionId: string,
|
|
212
|
-
sessionStorage?: SessionStorage
|
|
213
|
-
): boolean {
|
|
214
|
-
const storage = sessionStorage ?? getDefaultSessionStorage();
|
|
215
|
-
const session = storage.get(sessionId);
|
|
216
|
-
|
|
217
|
-
if (!session) return false;
|
|
218
|
-
|
|
219
|
-
if (Date.now() - session.timestamp > SESSION_TIMEOUT) {
|
|
220
|
-
storage.delete(sessionId);
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return session.verified;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Clean up a session after successful login
|
|
229
|
-
*/
|
|
230
|
-
export function cleanupSession(
|
|
231
|
-
sessionId: string,
|
|
232
|
-
sessionStorage?: SessionStorage
|
|
233
|
-
): void {
|
|
234
|
-
const storage = sessionStorage ?? getDefaultSessionStorage();
|
|
235
|
-
storage.delete(sessionId);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Get session data
|
|
240
|
-
*/
|
|
241
|
-
export function getSession(
|
|
242
|
-
sessionId: string,
|
|
243
|
-
sessionStorage?: SessionStorage
|
|
244
|
-
): EmailAuthSession | undefined {
|
|
245
|
-
const storage = sessionStorage ?? getDefaultSessionStorage();
|
|
246
|
-
const session = storage.get(sessionId);
|
|
247
|
-
|
|
248
|
-
if (!session) return undefined;
|
|
249
|
-
|
|
250
|
-
// Check if expired
|
|
251
|
-
if (Date.now() - session.timestamp > SESSION_TIMEOUT) {
|
|
252
|
-
storage.delete(sessionId);
|
|
253
|
-
return undefined;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return session;
|
|
257
|
-
}
|
|
258
|
-
|
package/src/server/index.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Server-side authentication utilities
|
|
3
|
-
*
|
|
4
|
-
* @example
|
|
5
|
-
* ```typescript
|
|
6
|
-
* import {
|
|
7
|
-
* createAuthMiddleware,
|
|
8
|
-
* initiateEmailAuth,
|
|
9
|
-
* verifyEmailAuth,
|
|
10
|
-
* signToken,
|
|
11
|
-
* verifyToken,
|
|
12
|
-
* createTurnkeyClient,
|
|
13
|
-
* TurnkeyWebVHSigner
|
|
14
|
-
* } from '@originals/auth/server';
|
|
15
|
-
* ```
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
export { createTurnkeyClient, getOrCreateTurnkeySubOrg } from './turnkey-client';
|
|
19
|
-
export {
|
|
20
|
-
initiateEmailAuth,
|
|
21
|
-
verifyEmailAuth,
|
|
22
|
-
isSessionVerified,
|
|
23
|
-
cleanupSession,
|
|
24
|
-
getSession,
|
|
25
|
-
type SessionStorage,
|
|
26
|
-
createInMemorySessionStorage,
|
|
27
|
-
} from './email-auth';
|
|
28
|
-
export {
|
|
29
|
-
signToken,
|
|
30
|
-
verifyToken,
|
|
31
|
-
getAuthCookieConfig,
|
|
32
|
-
getClearAuthCookieConfig,
|
|
33
|
-
} from './jwt';
|
|
34
|
-
export { createAuthMiddleware } from './middleware';
|
|
35
|
-
export { TurnkeyWebVHSigner, createTurnkeySigner } from './turnkey-signer';
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
package/src/server/jwt.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JWT Authentication Module
|
|
3
|
-
* Implements secure token issuance and validation with HTTP-only cookies
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import jwt from 'jsonwebtoken';
|
|
7
|
-
import type { TokenPayload, AuthCookieConfig } from '../types';
|
|
8
|
-
|
|
9
|
-
// 7 days in seconds
|
|
10
|
-
const DEFAULT_JWT_EXPIRES_IN = 7 * 24 * 60 * 60;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get JWT secret from config or environment
|
|
14
|
-
*/
|
|
15
|
-
function getJwtSecret(configSecret?: string): string {
|
|
16
|
-
const secret = configSecret ?? process.env.JWT_SECRET;
|
|
17
|
-
if (!secret) {
|
|
18
|
-
throw new Error('JWT_SECRET environment variable is required');
|
|
19
|
-
}
|
|
20
|
-
return secret;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Sign a JWT token for a user
|
|
25
|
-
* @param subOrgId - Turnkey sub-organization ID (stable identifier)
|
|
26
|
-
* @param email - User email (metadata)
|
|
27
|
-
* @param sessionToken - Optional Turnkey session token for user authentication
|
|
28
|
-
* @param options - Additional options
|
|
29
|
-
* @returns Signed JWT token string
|
|
30
|
-
*/
|
|
31
|
-
export function signToken(
|
|
32
|
-
subOrgId: string,
|
|
33
|
-
email: string,
|
|
34
|
-
sessionToken?: string,
|
|
35
|
-
options?: {
|
|
36
|
-
secret?: string;
|
|
37
|
-
expiresIn?: number;
|
|
38
|
-
issuer?: string;
|
|
39
|
-
audience?: string;
|
|
40
|
-
}
|
|
41
|
-
): string {
|
|
42
|
-
if (!subOrgId) {
|
|
43
|
-
throw new Error('Sub-organization ID is required for token signing');
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const secret = getJwtSecret(options?.secret);
|
|
47
|
-
|
|
48
|
-
const payload: Record<string, unknown> = {
|
|
49
|
-
sub: subOrgId,
|
|
50
|
-
email,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
if (sessionToken) {
|
|
54
|
-
payload.sessionToken = sessionToken;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const signOptions: jwt.SignOptions = {
|
|
58
|
-
expiresIn: options?.expiresIn ?? DEFAULT_JWT_EXPIRES_IN,
|
|
59
|
-
issuer: options?.issuer ?? 'originals-auth',
|
|
60
|
-
audience: options?.audience ?? 'originals-api',
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return jwt.sign(payload, secret, signOptions);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Verify and decode a JWT token
|
|
68
|
-
* @param token - JWT token string
|
|
69
|
-
* @param options - Additional options
|
|
70
|
-
* @returns Decoded token payload
|
|
71
|
-
* @throws Error if token is invalid or expired
|
|
72
|
-
*/
|
|
73
|
-
export function verifyToken(
|
|
74
|
-
token: string,
|
|
75
|
-
options?: {
|
|
76
|
-
secret?: string;
|
|
77
|
-
issuer?: string;
|
|
78
|
-
audience?: string;
|
|
79
|
-
}
|
|
80
|
-
): TokenPayload {
|
|
81
|
-
const secret = getJwtSecret(options?.secret);
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const payload = jwt.verify(token, secret, {
|
|
85
|
-
issuer: options?.issuer ?? 'originals-auth',
|
|
86
|
-
audience: options?.audience ?? 'originals-api',
|
|
87
|
-
}) as TokenPayload;
|
|
88
|
-
|
|
89
|
-
if (!payload.sub) {
|
|
90
|
-
throw new Error('Token missing sub-organization ID');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return payload;
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (error instanceof jwt.TokenExpiredError) {
|
|
96
|
-
throw new Error('Token has expired');
|
|
97
|
-
}
|
|
98
|
-
if (error instanceof jwt.JsonWebTokenError) {
|
|
99
|
-
throw new Error('Invalid token');
|
|
100
|
-
}
|
|
101
|
-
throw error;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Generate a secure cookie configuration for authentication tokens
|
|
107
|
-
* @param token - JWT token to set in cookie
|
|
108
|
-
* @param options - Cookie options
|
|
109
|
-
* @returns Cookie configuration object
|
|
110
|
-
*/
|
|
111
|
-
export function getAuthCookieConfig(
|
|
112
|
-
token: string,
|
|
113
|
-
options?: {
|
|
114
|
-
cookieName?: string;
|
|
115
|
-
maxAge?: number;
|
|
116
|
-
secure?: boolean;
|
|
117
|
-
}
|
|
118
|
-
): AuthCookieConfig {
|
|
119
|
-
const isProduction = process.env.NODE_ENV === 'production';
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
name: options?.cookieName ?? 'auth_token',
|
|
123
|
-
value: token,
|
|
124
|
-
options: {
|
|
125
|
-
httpOnly: true, // Cannot be accessed by JavaScript (XSS protection)
|
|
126
|
-
secure: options?.secure ?? isProduction, // HTTPS only in production
|
|
127
|
-
sameSite: 'strict', // CSRF protection
|
|
128
|
-
maxAge: options?.maxAge ?? 7 * 24 * 60 * 60 * 1000, // 7 days in milliseconds
|
|
129
|
-
path: '/', // Available for all routes
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get cookie configuration for logout (clears the auth cookie)
|
|
136
|
-
* @param cookieName - Name of the cookie to clear
|
|
137
|
-
* @returns Cookie configuration for clearing
|
|
138
|
-
*/
|
|
139
|
-
export function getClearAuthCookieConfig(cookieName?: string): AuthCookieConfig {
|
|
140
|
-
const isProduction = process.env.NODE_ENV === 'production';
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
name: cookieName ?? 'auth_token',
|
|
144
|
-
value: '',
|
|
145
|
-
options: {
|
|
146
|
-
httpOnly: true,
|
|
147
|
-
secure: isProduction,
|
|
148
|
-
sameSite: 'strict',
|
|
149
|
-
maxAge: 0, // Expire immediately
|
|
150
|
-
path: '/',
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|