Skip to content

Commit 64bdeca

Browse files
committed
service role support (#19)
1 parent aaff18b commit 64bdeca

File tree

6 files changed

+639
-40
lines changed

6 files changed

+639
-40
lines changed

README.md

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ import { createClient } from '@base44/sdk';
2020
// Create a client instance
2121
const base44 = createClient({
2222
serverUrl: 'https://base44.app', // Optional, defaults to 'https://base44.app'
23-
appId: 'your-app-id', // Required
24-
env: 'prod', // Optional, defaults to 'prod'
25-
token: 'your-token', // Optional
26-
autoInitAuth: true, // Optional, defaults to true - auto-detects tokens from URL or localStorage
23+
appId: 'your-app-id', // Required
24+
token: 'your-user-token', // Optional, for user authentication
25+
serviceToken: 'your-service-token', // Optional, for service role authentication
26+
autoInitAuth: true, // Optional, defaults to true - auto-detects tokens from URL or localStorage
2727
});
2828
```
2929

@@ -63,6 +63,77 @@ const newProducts = await base44.entities.Product.bulkCreate([
6363
]);
6464
```
6565

66+
### Service Role Authentication
67+
68+
Service role authentication allows server-side applications to perform operations with elevated privileges. This is useful for administrative tasks, background jobs, and server-to-server communication.
69+
70+
```javascript
71+
import { createClient } from '@base44/sdk';
72+
73+
// Create a client with service role token
74+
const base44 = createClient({
75+
appId: 'your-app-id',
76+
token: 'user-token', // For user operations
77+
serviceToken: 'service-token' // For service role operations
78+
});
79+
80+
// User operations (uses user token)
81+
const userEntities = await base44.entities.User.list();
82+
83+
// Service role operations (uses service token)
84+
const allEntities = await base44.asServiceRole.entities.User.list();
85+
86+
// Service role has access to:
87+
// - base44.asServiceRole.entities
88+
// - base44.asServiceRole.integrations
89+
// - base44.asServiceRole.functions
90+
// Note: Service role does NOT have access to auth module for security
91+
92+
// If no service token is provided, accessing asServiceRole throws an error
93+
const clientWithoutService = createClient({ appId: 'your-app-id' });
94+
try {
95+
await clientWithoutService.asServiceRole.entities.User.list();
96+
} catch (error) {
97+
// Error: Service token is required to use asServiceRole
98+
}
99+
```
100+
101+
### Server-Side Usage
102+
103+
For server-side applications, you can create a client from incoming HTTP requests:
104+
105+
```javascript
106+
import { createClientFromRequest } from '@base44/sdk';
107+
108+
// In your server handler (Express, Next.js, etc.)
109+
app.get('/api/data', async (req, res) => {
110+
try {
111+
// Extract client configuration from request headers
112+
const base44 = createClientFromRequest(req);
113+
114+
// Headers used:
115+
// - Authorization: Bearer <user-token>
116+
// - Base44-Service-Authorization: Bearer <service-token>
117+
// - Base44-App-Id: <app-id>
118+
// - Base44-Api-Url: <custom-api-url> (optional)
119+
120+
// Use appropriate authentication based on available tokens
121+
let data;
122+
if (base44.asServiceRole) {
123+
// Service token available - use elevated privileges
124+
data = await base44.asServiceRole.entities.SensitiveData.list();
125+
} else {
126+
// Only user token available - use user permissions
127+
data = await base44.entities.PublicData.list();
128+
}
129+
130+
res.json(data);
131+
} catch (error) {
132+
res.status(500).json({ error: error.message });
133+
}
134+
});
135+
```
136+
66137
### Working with Integrations
67138

68139
```javascript
@@ -103,7 +174,7 @@ import { getAccessToken } from '@base44/sdk/utils/auth-utils';
103174
// Create a client with authentication
104175
const base44 = createClient({
105176
appId: 'your-app-id',
106-
accessToken: getAccessToken() // Automatically retrieves token from localStorage or URL
177+
token: getAccessToken() // Automatically retrieves token from localStorage or URL
107178
});
108179

109180
// Check authentication status
@@ -167,7 +238,7 @@ function AuthProvider({ children }) {
167238
const [client] = useState(() =>
168239
createClient({
169240
appId: 'your-app-id',
170-
accessToken: getAccessToken()
241+
token: getAccessToken()
171242
})
172243
);
173244

@@ -347,6 +418,24 @@ async function fetchProducts() {
347418
}
348419
}
349420

421+
// Service role operations with TypeScript
422+
async function adminOperations() {
423+
const base44 = createClient({
424+
appId: 'your-app-id',
425+
serviceToken: 'service-token'
426+
});
427+
428+
// TypeScript knows asServiceRole requires a service token
429+
try {
430+
const allUsers: Entity[] = await base44.asServiceRole.entities.User.list();
431+
console.log(`Total users: ${allUsers.length}`);
432+
} catch (error) {
433+
if (error instanceof Error) {
434+
console.error(error.message); // Service token is required to use asServiceRole
435+
}
436+
}
437+
}
438+
350439
// Authentication with TypeScript
351440
async function handleAuth(auth: AuthModule) {
352441
// Check authentication
@@ -456,6 +545,25 @@ try {
456545
}
457546
```
458547

548+
## Functions
549+
550+
The SDK supports invoking custom functions:
551+
552+
```javascript
553+
// Invoke a function without parameters
554+
const result = await base44.functions.myFunction();
555+
556+
// Invoke a function with parameters
557+
const result = await base44.functions.calculateTotal({
558+
items: ['item1', 'item2'],
559+
discount: 0.1
560+
});
561+
562+
// Functions are automatically authenticated with the user token
563+
// Service role can also invoke functions
564+
const serviceResult = await base44.asServiceRole.functions.adminFunction();
565+
```
566+
459567
## Testing
460568

461569
The SDK includes comprehensive tests to ensure reliability.

src/client.ts

Lines changed: 99 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,41 @@ import { createFunctionsModule } from "./modules/functions.js";
1010
* @param {Object} config - Client configuration
1111
* @param {string} [config.serverUrl='https://base44.app'] - API server URL
1212
* @param {string|number} config.appId - Application ID
13-
* @param {string} [config.env='prod'] - Environment ('prod' or 'dev')
1413
* @param {string} [config.token] - Authentication token
14+
* @param {string} [config.serviceToken] - Service role authentication token
1515
* @param {boolean} [config.requiresAuth=false] - Whether the app requires authentication
1616
* @returns {Object} Base44 client instance
1717
*/
1818
export function createClient(config: {
1919
serverUrl?: string;
2020
appId: string;
21-
env?: string;
2221
token?: string;
22+
serviceToken?: string;
2323
requiresAuth?: boolean;
2424
}) {
2525
const {
2626
serverUrl = "https://base44.app",
2727
appId,
28-
env = "prod",
2928
token,
29+
serviceToken,
3030
requiresAuth = false,
3131
} = config;
3232

33-
// Create the base axios client
3433
const axiosClient = createAxiosClient({
3534
baseURL: `${serverUrl}/api`,
3635
headers: {
3736
"X-App-Id": String(appId),
38-
"X-Environment": env,
3937
},
4038
token,
41-
requiresAuth, // Pass requiresAuth to axios client
42-
appId, // Pass appId for login redirect
43-
serverUrl, // Pass serverUrl for login redirect
39+
requiresAuth,
40+
appId,
41+
serverUrl,
4442
});
4543

4644
const functionsAxiosClient = createAxiosClient({
4745
baseURL: `${serverUrl}/api`,
4846
headers: {
4947
"X-App-Id": String(appId),
50-
"X-Environment": env,
5148
},
5249
token,
5350
requiresAuth,
@@ -56,18 +53,46 @@ export function createClient(config: {
5653
interceptResponses: false,
5754
});
5855

59-
// Create modules
60-
const entities = createEntitiesModule(axiosClient, appId);
61-
const integrations = createIntegrationsModule(axiosClient, appId);
62-
const auth = createAuthModule(axiosClient, appId, serverUrl);
63-
const functions = createFunctionsModule(functionsAxiosClient, appId);
56+
const serviceRoleAxiosClient = createAxiosClient({
57+
baseURL: `${serverUrl}/api`,
58+
headers: {
59+
"X-App-Id": String(appId),
60+
},
61+
token: serviceToken,
62+
serverUrl,
63+
appId,
64+
});
65+
66+
const serviceRoleFunctionsAxiosClient = createAxiosClient({
67+
baseURL: `${serverUrl}/api`,
68+
headers: {
69+
"X-App-Id": String(appId),
70+
},
71+
token: serviceToken,
72+
serverUrl,
73+
appId,
74+
interceptResponses: false,
75+
});
76+
77+
const userModules = {
78+
entities: createEntitiesModule(axiosClient, appId),
79+
integrations: createIntegrationsModule(axiosClient, appId),
80+
auth: createAuthModule(axiosClient, functionsAxiosClient, appId),
81+
functions: createFunctionsModule(functionsAxiosClient, appId),
82+
};
83+
84+
const serviceRoleModules = {
85+
entities: createEntitiesModule(serviceRoleAxiosClient, appId),
86+
integrations: createIntegrationsModule(serviceRoleAxiosClient, appId),
87+
functions: createFunctionsModule(serviceRoleFunctionsAxiosClient, appId),
88+
};
6489

6590
// Always try to get token from localStorage or URL parameters
6691
if (typeof window !== "undefined") {
6792
// Get token from URL or localStorage
6893
const accessToken = token || getAccessToken();
6994
if (accessToken) {
70-
auth.setToken(accessToken);
95+
userModules.auth.setToken(accessToken);
7196
}
7297
}
7398

@@ -76,30 +101,27 @@ export function createClient(config: {
76101
// We perform this check asynchronously to not block client creation
77102
setTimeout(async () => {
78103
try {
79-
const isAuthenticated = await auth.isAuthenticated();
104+
const isAuthenticated = await userModules.auth.isAuthenticated();
80105
if (!isAuthenticated) {
81-
auth.redirectToLogin(window.location.href);
106+
userModules.auth.redirectToLogin(window.location.href);
82107
}
83108
} catch (error) {
84109
console.error("Authentication check failed:", error);
85-
auth.redirectToLogin(window.location.href);
110+
userModules.auth.redirectToLogin(window.location.href);
86111
}
87112
}, 0);
88113
}
89114

90115
// Assemble and return the client
91-
return {
92-
entities,
93-
integrations,
94-
auth,
95-
functions,
116+
const client = {
117+
...userModules,
96118

97119
/**
98120
* Set authentication token for all requests
99121
* @param {string} newToken - New auth token
100122
*/
101123
setToken(newToken: string) {
102-
auth.setToken(newToken);
124+
userModules.auth.setToken(newToken);
103125
},
104126

105127
/**
@@ -110,9 +132,61 @@ export function createClient(config: {
110132
return {
111133
serverUrl,
112134
appId,
113-
env,
114135
requiresAuth,
115136
};
116137
},
138+
139+
/**
140+
* Access service role modules - throws error if no service token was provided
141+
* @throws {Error} When accessed without a service token
142+
*/
143+
get asServiceRole() {
144+
if (!serviceToken) {
145+
throw new Error('Service token is required to use asServiceRole. Please provide a serviceToken when creating the client.');
146+
}
147+
return serviceRoleModules;
148+
}
117149
};
150+
151+
return client;
152+
}
153+
154+
export function createClientFromRequest(request: Request) {
155+
const authHeader = request.headers.get("Authorization");
156+
const serviceRoleAuthHeader = request.headers.get(
157+
"Base44-Service-Authorization"
158+
);
159+
const appId = request.headers.get("Base44-App-Id");
160+
const serverUrlHeader = request.headers.get("Base44-Api-Url");
161+
162+
if (!appId) {
163+
throw new Error(
164+
"Base44-App-Id header is required, but is was not found on the request"
165+
);
166+
}
167+
168+
// Validate authorization header formats
169+
let serviceRoleToken: string | undefined;
170+
let userToken: string | undefined;
171+
172+
if (serviceRoleAuthHeader !== null) {
173+
if (serviceRoleAuthHeader === '' || !serviceRoleAuthHeader.startsWith('Bearer ') || serviceRoleAuthHeader.split(' ').length !== 2) {
174+
throw new Error('Invalid authorization header format. Expected "Bearer <token>"');
175+
}
176+
serviceRoleToken = serviceRoleAuthHeader.split(' ')[1];
177+
}
178+
179+
if (authHeader !== null) {
180+
if (authHeader === '' || !authHeader.startsWith('Bearer ') || authHeader.split(' ').length !== 2) {
181+
throw new Error('Invalid authorization header format. Expected "Bearer <token>"');
182+
}
183+
userToken = authHeader.split(' ')[1];
184+
}
185+
186+
return createClient({
187+
serverUrl: serverUrlHeader || "https://base44.app",
188+
appId,
189+
token: userToken,
190+
serviceToken: serviceRoleToken,
191+
});
118192
}

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createClient } from "./client.js";
1+
import { createClient, createClientFromRequest } from "./client.js";
22
import { Base44Error } from "./utils/axios-client.js";
33
import {
44
getAccessToken,
@@ -9,8 +9,8 @@ import {
99

1010
export {
1111
createClient,
12+
createClientFromRequest,
1213
Base44Error,
13-
// Export auth utilities for easier access
1414
getAccessToken,
1515
saveAccessToken,
1616
removeAccessToken,

0 commit comments

Comments
 (0)