Function Integration
Access authenticated user context in your functions.
Access authenticated user context in your functions.
Receiving User Context
When a function is invoked with an auth user token, the user context is injected into the event:
exports.handler = async (event) => {
// Check if user is authenticated
if (!event.__volcano_auth) {
return {
statusCode: 401,
body: JSON.stringify({ error: 'Unauthorized' }),
};
}
// Access user information
const { user_id, email, project_id, role } = event.__volcano_auth;
console.log('User ID:', user_id);
console.log('Email:', email);
console.log('Project:', project_id);
console.log('Role:', role); // "authenticated" or "anonymous"
};User Context Fields
event.__volcano_auth = {
user_id: 'uuid', // Auth user's unique ID
email: 'user@example.com', // User's email
project_id: 'uuid', // Project the user belongs to
role: 'authenticated', // "authenticated" or "anonymous"
access_token: 'eyJhbGci...', // The caller's access token
};Role values:
"authenticated"- User signed up with email/password or OAuth"anonymous"- Guest user (created viasignUpAnonymous())
Note: Both user types have a user_id. When an anonymous user converts to authenticated, their user_id remains the same - preserving all their data.
With service key
If invoked with a service key (not auth user token), __volcano_auth is undefined:
if (!event.__volcano_auth) {
// Invoked with service key (service-to-service)
// Or no authentication
}Handling Different User Types
exports.handler = async (event) => {
const auth = event.__volcano_auth;
if (!auth) {
return { statusCode: 401, body: 'Unauthorized' };
}
// Handle based on user type
if (auth.role === 'anonymous') {
// Guest user - maybe limit features
return {
statusCode: 200,
body: JSON.stringify({
data: limitedData,
suggestion: 'Create an account to unlock all features',
}),
};
}
// Full authenticated user
return {
statusCode: 200,
body: JSON.stringify({ data: fullData }),
};
};Database Integration
DATABASE_URL defaults to application_name=volcano_full_access (admin, bypasses RLS). Rewrite
application_name to volcano_user_access:{user_id} before connecting, and pgproxy sets the
session variables Row-Level Security policies read automatically:
Save the bounded per-user pool helper as user-db-pool.js alongside your function. It retains at most four user pools with one connection each, reclaims idle pools for new users, removes empty pools after failed startup, and preserves active work. Handle its capacity error with backpressure and size all instances within the shared connection limits.
const { connectForUser } = require('./user-db-pool');
exports.handler = async (event) => {
if (!event.__volcano_auth) {
return { statusCode: 401, body: 'Unauthorized' };
}
const { user_id } = event.__volcano_auth;
let client;
try {
client = await connectForUser(user_id);
} catch (error) {
// Log diagnostic codes, never connection strings or error messages.
console.error('Database connection failed', { name: error?.name, code: error?.code ?? 'UNKNOWN' });
return {
statusCode: 503,
headers: { 'Retry-After': '1' },
body: JSON.stringify({ error: 'Database temporarily unavailable' }),
};
}
try {
// RLS policies work automatically — works for both authenticated and anonymous users
const result = await client.query('SELECT * FROM posts');
return {
statusCode: 200,
body: JSON.stringify(result.rows),
};
} finally {
client.release();
}
};Complete Example
const { connectForUser } = require('./user-db-pool');
exports.handler = async (event) => {
// 1. Verify authentication
if (!event.__volcano_auth) {
return { statusCode: 401, body: 'Unauthorized' };
}
const { user_id } = event.__volcano_auth;
// 2. Get a connection from this user's pool — RLS is already in effect
let client;
try {
client = await connectForUser(user_id);
} catch (error) {
// Log diagnostic codes, never connection strings or error messages.
console.error('Database connection failed', { name: error?.name, code: error?.code ?? 'UNKNOWN' });
return {
statusCode: 503,
headers: { 'Retry-After': '1' },
body: JSON.stringify({ error: 'Database temporarily unavailable' }),
};
}
try {
// 3. Handle different actions
const { action } = event;
switch (action) {
case 'create_post':
await client.query(
'INSERT INTO posts (title, content) VALUES ($1, $2)',
[event.title, event.content],
// user_id automatically set by trigger
);
return { statusCode: 201, body: 'Post created' };
case 'get_posts':
// RLS automatically filters to user's posts
// Works for both anonymous and authenticated users
const result = await client.query('SELECT * FROM posts');
return {
statusCode: 200,
body: JSON.stringify(result.rows),
};
default:
return { statusCode: 400, body: 'Unknown action' };
}
} finally {
client.release();
}
};Environment Variables
Volcano doesn't set DATABASE_URL for you. Set it yourself as a project variable, from the connection string in Connection strings:
process.env.DATABASE_URL; // The connection string you set as a project variableSee Also
- Row-Level Security - Secure data at database level
- Auth Helpers - Using auth.uid(), auth.email()
- Examples - Complete working examples