squarefi-bff-api-module 1.32.1 → 1.32.2
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/dist/api/auth.d.ts +29 -0
- package/dist/api/auth.js +59 -0
- package/dist/api/bank-data.d.ts +4 -0
- package/dist/api/bank-data.js +6 -0
- package/dist/api/counterparties.d.ts +14 -0
- package/dist/api/counterparties.js +16 -0
- package/dist/api/developer.d.ts +12 -0
- package/dist/api/developer.js +12 -0
- package/dist/api/exchange.d.ts +14 -0
- package/dist/api/exchange.js +20 -0
- package/dist/api/frontend.d.ts +11 -0
- package/dist/api/frontend.js +11 -0
- package/dist/api/index.d.ts +38 -0
- package/dist/api/index.js +36 -0
- package/dist/api/issuing.d.ts +64 -0
- package/dist/api/issuing.js +140 -0
- package/dist/api/kyc.d.ts +21 -0
- package/dist/api/kyc.js +21 -0
- package/dist/api/list.d.ts +16 -0
- package/dist/api/list.js +16 -0
- package/dist/api/orders.d.ts +49 -0
- package/dist/api/orders.js +84 -0
- package/dist/api/persona.d.ts +7 -0
- package/dist/api/persona.js +7 -0
- package/dist/api/storage.d.ts +8 -0
- package/dist/api/storage.js +16 -0
- package/dist/api/tenants.d.ts +6 -0
- package/dist/api/tenants.js +6 -0
- package/dist/api/totp.d.ts +17 -0
- package/dist/api/totp.js +45 -0
- package/{src/api/types/autogen/apiV2.types.ts → dist/api/types/autogen/apiV2.types.d.ts} +0 -1
- package/dist/api/types/autogen/apiV2.types.js +5 -0
- package/dist/api/types/types.d.ts +2258 -0
- package/dist/api/types/types.js +1 -0
- package/dist/api/user.d.ts +18 -0
- package/dist/api/user.js +18 -0
- package/dist/api/virtual-accounts.d.ts +9 -0
- package/dist/api/virtual-accounts.js +9 -0
- package/dist/api/wallets.d.ts +24 -0
- package/dist/api/wallets.js +30 -0
- package/dist/constants.d.ts +303 -0
- package/dist/constants.js +332 -0
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/useCalc.d.ts +25 -0
- package/dist/hooks/useCalc.js +115 -0
- package/dist/hooks/useFileUpload.d.ts +49 -0
- package/dist/hooks/useFileUpload.js +100 -0
- package/dist/hooks/useSupabaseSubscription/config.d.ts +2 -0
- package/dist/hooks/useSupabaseSubscription/config.js +5 -0
- package/dist/hooks/useSupabaseSubscription/index.js +2 -0
- package/dist/hooks/useSupabaseSubscription/specialized.d.ts +5 -0
- package/{src/hooks/useSupabaseSubscription/specialized.ts → dist/hooks/useSupabaseSubscription/specialized.js} +2 -5
- package/dist/hooks/useSupabaseSubscription/types.d.ts +16 -0
- package/dist/hooks/useSupabaseSubscription/types.js +1 -0
- package/dist/hooks/useSupabaseSubscription/useSupabaseSubscription.d.ts +5 -0
- package/dist/hooks/useSupabaseSubscription/useSupabaseSubscription.js +37 -0
- package/dist/index.d.ts +7 -0
- package/dist/utils/apiClientFactory.d.ts +31 -0
- package/dist/utils/apiClientFactory.js +138 -0
- package/dist/utils/converters.d.ts +1 -0
- package/dist/utils/converters.js +1 -0
- package/dist/utils/encrypt.d.ts +10 -0
- package/dist/utils/encrypt.js +77 -0
- package/dist/utils/fileStorage.d.ts +120 -0
- package/dist/utils/fileStorage.js +292 -0
- package/dist/utils/storage.d.ts +3 -0
- package/dist/utils/storage.js +24 -0
- package/dist/utils/supabase.d.ts +1 -0
- package/dist/utils/supabase.js +12 -0
- package/dist/utils/tokensFactory.d.ts +12 -0
- package/dist/utils/tokensFactory.js +42 -0
- package/package.json +4 -1
- package/.env.example +0 -1
- package/.husky/pre-commit +0 -2
- package/.prettierignore +0 -6
- package/.prettierrc +0 -7
- package/CHANGELOG.md +0 -1415
- package/FIXED_RLS_ERROR.md +0 -146
- package/QUICK_TEST.md +0 -127
- package/STORAGE_MODULE_SUMMARY.md +0 -228
- package/TEST_INSTRUCTIONS.md +0 -122
- package/docs/AUTH_TOKEN_USAGE.md +0 -290
- package/docs/BACKEND_SERVICE_URL.md +0 -334
- package/docs/FRONTEND_STORAGE_GUIDE.md +0 -529
- package/docs/STORAGE_MODULE.md +0 -490
- package/docs/STORAGE_QUICK_START.md +0 -76
- package/scripts/generate-openapi-types.ts +0 -41
- package/scripts/supabase-storage-setup.sql +0 -223
- package/src/api/auth.ts +0 -78
- package/src/api/bank-data.ts +0 -11
- package/src/api/counterparties.ts +0 -73
- package/src/api/developer.ts +0 -20
- package/src/api/exchange.ts +0 -44
- package/src/api/frontend.ts +0 -20
- package/src/api/index.ts +0 -57
- package/src/api/issuing.ts +0 -214
- package/src/api/kyc.ts +0 -41
- package/src/api/list.ts +0 -26
- package/src/api/orders.ts +0 -255
- package/src/api/persona.ts +0 -16
- package/src/api/storage.ts +0 -24
- package/src/api/tenants.ts +0 -8
- package/src/api/totp.ts +0 -51
- package/src/api/types/types.ts +0 -2820
- package/src/api/user.ts +0 -27
- package/src/api/virtual-accounts.ts +0 -15
- package/src/api/wallets.ts +0 -65
- package/src/constants.ts +0 -343
- package/src/hooks/useCalc.ts +0 -181
- package/src/hooks/useFileUpload.ts +0 -129
- package/src/hooks/useSupabaseSubscription/config.ts +0 -7
- package/src/hooks/useSupabaseSubscription/types.ts +0 -18
- package/src/hooks/useSupabaseSubscription/useSupabaseSubscription.ts +0 -53
- package/src/utils/apiClientFactory.ts +0 -194
- package/src/utils/converters.ts +0 -1
- package/src/utils/encrypt.ts +0 -96
- package/src/utils/fileStorage.ts +0 -353
- package/src/utils/storage.ts +0 -29
- package/src/utils/supabase.ts +0 -16
- package/src/utils/tokensFactory.ts +0 -59
- package/tsconfig.json +0 -15
- package/types.d.ts +0 -11
- /package/{src/hooks/index.ts → dist/hooks/index.d.ts} +0 -0
- /package/{src/hooks/useSupabaseSubscription/index.ts → dist/hooks/useSupabaseSubscription/index.d.ts} +0 -0
- /package/{src/index.ts → dist/index.js} +0 -0
package/docs/AUTH_TOKEN_USAGE.md
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
# Using Storage Module with Authentication
|
|
2
|
-
|
|
3
|
-
## Problem
|
|
4
|
-
When you see error: **"new row violates row-level security policy"**
|
|
5
|
-
|
|
6
|
-
This means:
|
|
7
|
-
- ✅ RLS policies are working (good!)
|
|
8
|
-
- ❌ User is not authenticated
|
|
9
|
-
|
|
10
|
-
## Solution: Pass authToken
|
|
11
|
-
|
|
12
|
-
All storage functions now accept an optional `authToken` parameter.
|
|
13
|
-
|
|
14
|
-
## How to Get Auth Token
|
|
15
|
-
|
|
16
|
-
### From Supabase Auth
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
import { supabaseClient } from 'squarefi-bff-api-module';
|
|
20
|
-
|
|
21
|
-
// After user login
|
|
22
|
-
const { data: { session } } = await supabaseClient.auth.getSession();
|
|
23
|
-
const authToken = session?.access_token;
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### From Your Own Auth System
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
// From JWT token in localStorage/cookies
|
|
30
|
-
const authToken = localStorage.getItem('access_token');
|
|
31
|
-
|
|
32
|
-
// Or from your auth context
|
|
33
|
-
const { accessToken } = useAuth();
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Usage Examples
|
|
37
|
-
|
|
38
|
-
### Direct API Usage
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
import { uploadFile } from 'squarefi-bff-api-module';
|
|
42
|
-
|
|
43
|
-
const authToken = 'your-jwt-token-here';
|
|
44
|
-
|
|
45
|
-
const result = await uploadFile({
|
|
46
|
-
file: myFile,
|
|
47
|
-
fileName: 'document.pdf',
|
|
48
|
-
userId: 'user-123',
|
|
49
|
-
authToken, // ← Передаем токен здесь!
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
if (result.success) {
|
|
53
|
-
console.log('Uploaded:', result.path);
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### With React Hooks
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
import { useFileUpload, useUserFiles } from 'squarefi-bff-api-module';
|
|
61
|
-
|
|
62
|
-
function MyComponent() {
|
|
63
|
-
// Get token from your auth
|
|
64
|
-
const { accessToken } = useAuth(); // your auth hook
|
|
65
|
-
|
|
66
|
-
const { upload, uploading } = useFileUpload({
|
|
67
|
-
userId: 'user-123',
|
|
68
|
-
authToken: accessToken, // ← Передаем токен здесь!
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const { files } = useUserFiles({
|
|
72
|
-
userId: 'user-123',
|
|
73
|
-
authToken: accessToken, // ← И здесь!
|
|
74
|
-
autoLoad: true,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div>
|
|
79
|
-
<input type="file" onChange={(e) => upload(e.target.files[0])} />
|
|
80
|
-
{files.map(f => <div key={f.id}>{f.name}</div>)}
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
### Complete Example with Supabase Auth
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
import React, { useEffect, useState } from 'react';
|
|
90
|
-
import { supabaseClient, useFileUpload } from 'squarefi-bff-api-module';
|
|
91
|
-
|
|
92
|
-
function AuthenticatedFileUpload() {
|
|
93
|
-
const [authToken, setAuthToken] = useState<string | null>(null);
|
|
94
|
-
const [userId, setUserId] = useState<string | null>(null);
|
|
95
|
-
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
// Get current session
|
|
98
|
-
supabaseClient?.auth.getSession().then(({ data: { session } }) => {
|
|
99
|
-
setAuthToken(session?.access_token || null);
|
|
100
|
-
setUserId(session?.user?.id || null);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Listen for auth changes
|
|
104
|
-
const { data: authListener } = supabaseClient?.auth.onAuthStateChange(
|
|
105
|
-
(_event, session) => {
|
|
106
|
-
setAuthToken(session?.access_token || null);
|
|
107
|
-
setUserId(session?.user?.id || null);
|
|
108
|
-
}
|
|
109
|
-
) || {};
|
|
110
|
-
|
|
111
|
-
return () => {
|
|
112
|
-
authListener?.subscription.unsubscribe();
|
|
113
|
-
};
|
|
114
|
-
}, []);
|
|
115
|
-
|
|
116
|
-
const { upload, uploading, error } = useFileUpload({
|
|
117
|
-
userId: userId || '',
|
|
118
|
-
authToken: authToken || undefined,
|
|
119
|
-
onSuccess: (result) => {
|
|
120
|
-
console.log('File uploaded:', result.path);
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
if (!authToken) {
|
|
125
|
-
return <div>Please log in first</div>;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<div>
|
|
130
|
-
<input
|
|
131
|
-
type="file"
|
|
132
|
-
onChange={(e) => upload(e.target.files?.[0])}
|
|
133
|
-
disabled={uploading}
|
|
134
|
-
/>
|
|
135
|
-
{error && <p style={{color: 'red'}}>{error}</p>}
|
|
136
|
-
</div>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## All Functions Support authToken
|
|
142
|
-
|
|
143
|
-
### Upload
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
uploadFile({ file, fileName, userId, authToken });
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### Get Signed URL
|
|
150
|
-
|
|
151
|
-
```typescript
|
|
152
|
-
getSignedUrl({ path, expiresIn, authToken });
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
### List Files
|
|
156
|
-
|
|
157
|
-
```typescript
|
|
158
|
-
listUserFiles(userId, bucket, authToken);
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Delete File
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
deleteFile(filePath, bucket, authToken);
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
### Delete Multiple Files
|
|
168
|
-
|
|
169
|
-
```typescript
|
|
170
|
-
deleteFiles(filePaths, bucket, authToken);
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Download File
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
downloadFile(filePath, bucket, authToken);
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Testing in Browser
|
|
180
|
-
|
|
181
|
-
Update `test-storage.html`:
|
|
182
|
-
|
|
183
|
-
```javascript
|
|
184
|
-
// Add auth token input
|
|
185
|
-
<input type="text" id="authToken" placeholder="Auth Token (JWT)" style="margin: 10px 0;">
|
|
186
|
-
|
|
187
|
-
// In upload function:
|
|
188
|
-
const authToken = document.getElementById('authToken').value;
|
|
189
|
-
|
|
190
|
-
const { data, error } = await supabaseClient.storage
|
|
191
|
-
.from(DEFAULT_BUCKET)
|
|
192
|
-
.upload(filePath, file);
|
|
193
|
-
|
|
194
|
-
// Or create authenticated client:
|
|
195
|
-
if (authToken) {
|
|
196
|
-
const { createClient } = window.supabase;
|
|
197
|
-
const authenticatedClient = createClient(SUPABASE_URL, SUPABASE_PUBLIC_KEY, {
|
|
198
|
-
global: {
|
|
199
|
-
headers: {
|
|
200
|
-
Authorization: `Bearer ${authToken}`
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Use authenticatedClient for upload
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
## How to Get Test Token
|
|
210
|
-
|
|
211
|
-
### Option 1: From Browser Console
|
|
212
|
-
|
|
213
|
-
```javascript
|
|
214
|
-
// After logging in to your app
|
|
215
|
-
const session = await supabaseClient.auth.getSession();
|
|
216
|
-
console.log('Token:', session.data.session.access_token);
|
|
217
|
-
// Copy this token and use it in test
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Option 2: Create Test User
|
|
221
|
-
|
|
222
|
-
```sql
|
|
223
|
-
-- In Supabase SQL Editor
|
|
224
|
-
-- Get user ID after signup
|
|
225
|
-
SELECT id FROM auth.users WHERE email = 'test@example.com';
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
Then login via API and get token.
|
|
229
|
-
|
|
230
|
-
## RLS Policies Explanation
|
|
231
|
-
|
|
232
|
-
The policies check `auth.uid()` which comes from the JWT token:
|
|
233
|
-
|
|
234
|
-
```sql
|
|
235
|
-
-- User can upload to their own folder
|
|
236
|
-
CREATE POLICY "Users can upload" ON storage.objects
|
|
237
|
-
FOR INSERT TO authenticated
|
|
238
|
-
WITH CHECK (
|
|
239
|
-
bucket_id = 'user-files'
|
|
240
|
-
AND (storage.foldername(name))[1] = auth.uid()::text
|
|
241
|
-
);
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
Without `authToken`, `auth.uid()` is NULL → policy fails.
|
|
245
|
-
|
|
246
|
-
With `authToken`, `auth.uid()` returns user ID from token → policy passes if user ID matches folder.
|
|
247
|
-
|
|
248
|
-
## Common Issues
|
|
249
|
-
|
|
250
|
-
### Still getting RLS error?
|
|
251
|
-
|
|
252
|
-
1. ✅ Check token is valid (not expired)
|
|
253
|
-
2. ✅ Check userId matches token's user ID
|
|
254
|
-
3. ✅ Check file path starts with correct userId: `{userId}/filename`
|
|
255
|
-
4. ✅ Verify SQL policies are correctly set up
|
|
256
|
-
|
|
257
|
-
### Token expired?
|
|
258
|
-
|
|
259
|
-
```typescript
|
|
260
|
-
// Refresh token
|
|
261
|
-
const { data, error } = await supabaseClient.auth.refreshSession();
|
|
262
|
-
if (data.session) {
|
|
263
|
-
const newToken = data.session.access_token;
|
|
264
|
-
// Use new token
|
|
265
|
-
}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
### Wrong user ID?
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
// Get user ID from token
|
|
272
|
-
const { data: { user } } = await supabaseClient.auth.getUser();
|
|
273
|
-
console.log('User ID:', user?.id);
|
|
274
|
-
// Use this ID for file paths
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
## Summary
|
|
278
|
-
|
|
279
|
-
✅ **Always pass `authToken` when using private buckets**
|
|
280
|
-
✅ **Token must match the user ID in file path**
|
|
281
|
-
✅ **Get token from Supabase Auth or your auth system**
|
|
282
|
-
✅ **All functions support optional `authToken` parameter**
|
|
283
|
-
|
|
284
|
-
## Related Files
|
|
285
|
-
|
|
286
|
-
- `src/utils/fileStorage.ts` - Core functions with authToken support
|
|
287
|
-
- `src/hooks/useFileUpload.ts` - React hook with authToken
|
|
288
|
-
- `src/hooks/useUserFiles.ts` - React hook with authToken
|
|
289
|
-
|
|
290
|
-
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
# Backend Public URL Usage
|
|
2
|
-
|
|
3
|
-
Guide for superadmin backend access to user files using permanent public URLs with service role key.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
For backend applications that need permanent access to files (e.g., admin dashboards, automated processing), use **Public URLs** from `getPublicUrl()` with Supabase service role key.
|
|
8
|
-
|
|
9
|
-
## Quick Example
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import { getPublicUrl, DEFAULT_BUCKET } from 'squarefi-bff-api-module';
|
|
13
|
-
|
|
14
|
-
// 1. Get permanent URL (can be done on frontend or backend)
|
|
15
|
-
const filePath = 'user-123/document.pdf';
|
|
16
|
-
const publicUrl = getPublicUrl(filePath, DEFAULT_BUCKET);
|
|
17
|
-
|
|
18
|
-
console.log(publicUrl);
|
|
19
|
-
// Output: https://xxx.supabase.co/storage/v1/object/public/user-files/user-123/document.pdf
|
|
20
|
-
|
|
21
|
-
// 2. Access file on backend with service role key
|
|
22
|
-
const response = await fetch(publicUrl, {
|
|
23
|
-
headers: {
|
|
24
|
-
'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (response.ok) {
|
|
29
|
-
const fileBlob = await response.blob();
|
|
30
|
-
// Process file...
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Key Differences
|
|
35
|
-
|
|
36
|
-
### Signed URL vs Public URL
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
// Signed URL - expires after 1 hour, no auth required
|
|
40
|
-
const signedUrl = await getSignedUrl({
|
|
41
|
-
path: 'user-123/file.pdf',
|
|
42
|
-
expiresIn: 3600
|
|
43
|
-
});
|
|
44
|
-
// Use case: temporary link for end user
|
|
45
|
-
|
|
46
|
-
// Public URL - permanent, requires service role key
|
|
47
|
-
const publicUrl = getPublicUrl('user-123/file.pdf');
|
|
48
|
-
// Use case: backend processing, admin access
|
|
49
|
-
// Must use: fetch(publicUrl, { headers: { Authorization: Bearer SERVICE_KEY } })
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Security Requirements
|
|
53
|
-
|
|
54
|
-
### ⚠️ Critical: Service Role Key
|
|
55
|
-
|
|
56
|
-
- **NEVER expose** `SUPABASE_SERVICE_ROLE_KEY` on frontend
|
|
57
|
-
- Store in secure environment variables
|
|
58
|
-
- Use only on backend/server
|
|
59
|
-
- This key **bypasses all RLS policies**
|
|
60
|
-
|
|
61
|
-
### Environment Setup
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
# Copy env.example to .env and fill in your values
|
|
65
|
-
# See env.example in repository root for all available variables
|
|
66
|
-
|
|
67
|
-
# Backend only - NEVER commit to git
|
|
68
|
-
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Usage Examples
|
|
72
|
-
|
|
73
|
-
### Node.js/Express Backend
|
|
74
|
-
|
|
75
|
-
```javascript
|
|
76
|
-
import express from 'express';
|
|
77
|
-
import { getPublicUrl, DEFAULT_BUCKET } from 'squarefi-bff-api-module';
|
|
78
|
-
|
|
79
|
-
const app = express();
|
|
80
|
-
|
|
81
|
-
// Admin endpoint to access any user file
|
|
82
|
-
app.get('/admin/files/:userId/:fileName', async (req, res) => {
|
|
83
|
-
const { userId, fileName } = req.params;
|
|
84
|
-
const filePath = `${userId}/${fileName}`;
|
|
85
|
-
|
|
86
|
-
// Get permanent URL
|
|
87
|
-
const publicUrl = getPublicUrl(filePath, DEFAULT_BUCKET);
|
|
88
|
-
|
|
89
|
-
// Fetch with service key
|
|
90
|
-
const response = await fetch(publicUrl, {
|
|
91
|
-
headers: {
|
|
92
|
-
'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
if (!response.ok) {
|
|
97
|
-
return res.status(404).json({ error: 'File not found' });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Stream file to client
|
|
101
|
-
const buffer = await response.arrayBuffer();
|
|
102
|
-
res.setHeader('Content-Type', response.headers.get('content-type'));
|
|
103
|
-
res.send(Buffer.from(buffer));
|
|
104
|
-
});
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Next.js API Route
|
|
108
|
-
|
|
109
|
-
```typescript
|
|
110
|
-
// pages/api/admin/file/[userId]/[fileName].ts
|
|
111
|
-
import { NextApiRequest, NextApiResponse } from 'next';
|
|
112
|
-
import { getPublicUrl, DEFAULT_BUCKET } from 'squarefi-bff-api-module';
|
|
113
|
-
|
|
114
|
-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
115
|
-
const { userId, fileName } = req.query;
|
|
116
|
-
const filePath = `${userId}/${fileName}`;
|
|
117
|
-
|
|
118
|
-
const publicUrl = getPublicUrl(filePath, DEFAULT_BUCKET);
|
|
119
|
-
|
|
120
|
-
const response = await fetch(publicUrl, {
|
|
121
|
-
headers: {
|
|
122
|
-
'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
if (!response.ok) {
|
|
127
|
-
return res.status(404).json({ error: 'File not found' });
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const buffer = await response.arrayBuffer();
|
|
131
|
-
res.setHeader('Content-Type', response.headers.get('content-type') || 'application/octet-stream');
|
|
132
|
-
res.send(Buffer.from(buffer));
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
### Background Job/Worker
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
import { getPublicUrl, DOCUMENTS_BUCKET } from 'squarefi-bff-api-module';
|
|
140
|
-
|
|
141
|
-
async function processUserDocument(userId: string, fileName: string) {
|
|
142
|
-
const filePath = `${userId}/${fileName}`;
|
|
143
|
-
const publicUrl = getPublicUrl(filePath, DOCUMENTS_BUCKET);
|
|
144
|
-
|
|
145
|
-
// Download file
|
|
146
|
-
const response = await fetch(publicUrl, {
|
|
147
|
-
headers: {
|
|
148
|
-
'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
if (!response.ok) {
|
|
153
|
-
throw new Error(`Failed to download file: ${response.statusText}`);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const fileContent = await response.text();
|
|
157
|
-
|
|
158
|
-
// Process file...
|
|
159
|
-
console.log('Processing document for user:', userId);
|
|
160
|
-
|
|
161
|
-
// Your processing logic here
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Run in worker/cron job
|
|
165
|
-
await processUserDocument('user-123', 'invoice.pdf');
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Admin Dashboard
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
// Frontend gets the URL, backend fetches the file
|
|
172
|
-
import { getPublicUrl, DEFAULT_BUCKET } from 'squarefi-bff-api-module';
|
|
173
|
-
|
|
174
|
-
// Frontend component
|
|
175
|
-
function AdminFileViewer({ userId, fileName }) {
|
|
176
|
-
const [fileUrl, setFileUrl] = useState(null);
|
|
177
|
-
|
|
178
|
-
useEffect(() => {
|
|
179
|
-
// Get the URL (can be done on frontend)
|
|
180
|
-
const publicUrl = getPublicUrl(`${userId}/${fileName}`, DEFAULT_BUCKET);
|
|
181
|
-
|
|
182
|
-
// Call your backend to proxy the request
|
|
183
|
-
fetch('/api/admin/proxy-file', {
|
|
184
|
-
method: 'POST',
|
|
185
|
-
headers: { 'Content-Type': 'application/json' },
|
|
186
|
-
body: JSON.stringify({ publicUrl })
|
|
187
|
-
})
|
|
188
|
-
.then(res => res.blob())
|
|
189
|
-
.then(blob => {
|
|
190
|
-
const url = URL.createObjectURL(blob);
|
|
191
|
-
setFileUrl(url);
|
|
192
|
-
});
|
|
193
|
-
}, [userId, fileName]);
|
|
194
|
-
|
|
195
|
-
return fileUrl ? <img src={fileUrl} alt="File" /> : <p>Loading...</p>;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Backend proxy endpoint (keeps service key secure)
|
|
199
|
-
app.post('/api/admin/proxy-file', async (req, res) => {
|
|
200
|
-
const { publicUrl } = req.body;
|
|
201
|
-
|
|
202
|
-
const response = await fetch(publicUrl, {
|
|
203
|
-
headers: {
|
|
204
|
-
'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
const buffer = await response.arrayBuffer();
|
|
209
|
-
res.setHeader('Content-Type', response.headers.get('content-type'));
|
|
210
|
-
res.send(Buffer.from(buffer));
|
|
211
|
-
});
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
## Best Practices
|
|
215
|
-
|
|
216
|
-
### ✅ DO
|
|
217
|
-
|
|
218
|
-
- Store service role key in secure environment variables
|
|
219
|
-
- Access public URLs with service key only on backend
|
|
220
|
-
- Validate user permissions before accessing files
|
|
221
|
-
- Log all admin file access for audit trail
|
|
222
|
-
- Use proper error handling
|
|
223
|
-
|
|
224
|
-
### ❌ DON'T
|
|
225
|
-
|
|
226
|
-
- Never expose service role key on frontend
|
|
227
|
-
- Don't commit service key to version control
|
|
228
|
-
- Don't use service key on client-side (URL itself is fine, but don't add auth header)
|
|
229
|
-
- Don't skip audit logging
|
|
230
|
-
- Don't share service key across environments (use different keys for dev/prod)
|
|
231
|
-
|
|
232
|
-
## Error Handling
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
async function downloadFileWithServiceKey(publicUrl: string) {
|
|
236
|
-
try {
|
|
237
|
-
const response = await fetch(publicUrl, {
|
|
238
|
-
headers: {
|
|
239
|
-
'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
if (!response.ok) {
|
|
244
|
-
if (response.status === 404) {
|
|
245
|
-
throw new Error('File not found');
|
|
246
|
-
}
|
|
247
|
-
if (response.status === 401) {
|
|
248
|
-
throw new Error('Invalid service key');
|
|
249
|
-
}
|
|
250
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return await response.blob();
|
|
254
|
-
} catch (error) {
|
|
255
|
-
console.error('Failed to download file:', error);
|
|
256
|
-
throw error;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
## Audit Logging Example
|
|
262
|
-
|
|
263
|
-
```typescript
|
|
264
|
-
import { getPublicUrl } from 'squarefi-bff-api-module';
|
|
265
|
-
|
|
266
|
-
async function auditedFileAccess(
|
|
267
|
-
adminUserId: string,
|
|
268
|
-
targetUserId: string,
|
|
269
|
-
fileName: string
|
|
270
|
-
) {
|
|
271
|
-
// Log access
|
|
272
|
-
await logAudit({
|
|
273
|
-
action: 'FILE_ACCESS',
|
|
274
|
-
adminId: adminUserId,
|
|
275
|
-
targetUserId,
|
|
276
|
-
fileName,
|
|
277
|
-
timestamp: new Date(),
|
|
278
|
-
ip: req.ip
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Get and access file
|
|
282
|
-
const publicUrl = getPublicUrl(`${targetUserId}/${fileName}`);
|
|
283
|
-
|
|
284
|
-
const response = await fetch(publicUrl, {
|
|
285
|
-
headers: {
|
|
286
|
-
'Authorization': `Bearer ${process.env.SUPABASE_SERVICE_ROLE_KEY}`
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
return response;
|
|
291
|
-
}
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
## Troubleshooting
|
|
295
|
-
|
|
296
|
-
### File Not Found (404)
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
// Check if file path is correct
|
|
300
|
-
const correctPath = `${userId}/${fileName}`; // ✅
|
|
301
|
-
const wrongPath = `${fileName}`; // ❌
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### Unauthorized (401)
|
|
305
|
-
|
|
306
|
-
- Verify `SUPABASE_SERVICE_ROLE_KEY` is set correctly
|
|
307
|
-
- Check if key is valid in Supabase Dashboard → Settings → API
|
|
308
|
-
|
|
309
|
-
### CORS Issues
|
|
310
|
-
|
|
311
|
-
If accessing from browser (not recommended but possible):
|
|
312
|
-
|
|
313
|
-
```typescript
|
|
314
|
-
// In Supabase Dashboard → Storage → Policies
|
|
315
|
-
// Make sure CORS is configured if needed
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## Summary
|
|
319
|
-
|
|
320
|
-
| Aspect | Details |
|
|
321
|
-
|--------|---------|
|
|
322
|
-
| **Function** | `getPublicUrl(path, bucket)` |
|
|
323
|
-
| **Returns** | Permanent URL string |
|
|
324
|
-
| **Authentication** | Required: `Authorization: Bearer SERVICE_ROLE_KEY` (for private buckets) |
|
|
325
|
-
| **Expiration** | Never expires |
|
|
326
|
-
| **Use Case** | Backend/admin access to private buckets |
|
|
327
|
-
| **Security** | ⚠️ NEVER expose service key on frontend |
|
|
328
|
-
|
|
329
|
-
## Related Documentation
|
|
330
|
-
|
|
331
|
-
- [Full Storage Module Docs](./STORAGE_MODULE.md)
|
|
332
|
-
- [Frontend Guide](./FRONTEND_STORAGE_GUIDE.md)
|
|
333
|
-
- [Quick Start](./STORAGE_QUICK_START.md)
|
|
334
|
-
|