122 lines
3.7 KiB
Markdown
122 lines
3.7 KiB
Markdown
---
|
|
title: Use Impersonation for Admin Operations
|
|
impact: MEDIUM
|
|
impactDescription: Safe admin access to user data without password sharing
|
|
tags: authentication, admin, impersonation, superuser
|
|
---
|
|
|
|
## Use Impersonation for Admin Operations
|
|
|
|
Impersonation allows superusers to generate tokens for other users, enabling admin support tasks and API key functionality without sharing passwords.
|
|
|
|
**Incorrect (sharing credentials or bypassing auth):**
|
|
|
|
```javascript
|
|
// Bad: sharing user passwords for support
|
|
async function helpUser(userId, userPassword) {
|
|
await pb.collection('users').authWithPassword(userEmail, userPassword);
|
|
// Support team knows user's password!
|
|
}
|
|
|
|
// Bad: directly modifying records without proper context
|
|
async function fixUserData(userId) {
|
|
// Bypasses user's perspective and rules
|
|
await pb.collection('posts').update(postId, { fixed: true });
|
|
}
|
|
```
|
|
|
|
**Correct (using impersonation):**
|
|
|
|
```javascript
|
|
import PocketBase from 'pocketbase';
|
|
|
|
// Admin client with superuser auth (use environment variables, never hardcode)
|
|
const adminPb = new PocketBase(process.env.PB_URL);
|
|
await adminPb.collection('_superusers').authWithPassword(
|
|
process.env.PB_SUPERUSER_EMAIL,
|
|
process.env.PB_SUPERUSER_PASSWORD
|
|
);
|
|
|
|
async function impersonateUser(userId) {
|
|
// Generate impersonation token (non-renewable)
|
|
const impersonatedClient = await adminPb
|
|
.collection('users')
|
|
.impersonate(userId, 3600); // 1 hour duration
|
|
|
|
// impersonatedClient has user's auth context
|
|
console.log('Acting as:', impersonatedClient.authStore.record.email);
|
|
|
|
// Operations use user's permissions
|
|
const userPosts = await impersonatedClient.collection('posts').getList();
|
|
|
|
return impersonatedClient;
|
|
}
|
|
|
|
// Use case: Admin viewing user's data
|
|
async function adminViewUserPosts(userId) {
|
|
const userClient = await impersonateUser(userId);
|
|
|
|
// See exactly what the user sees (respects API rules)
|
|
const posts = await userClient.collection('posts').getList();
|
|
|
|
return posts;
|
|
}
|
|
|
|
// Use case: API keys for server-to-server communication
|
|
async function createApiKey(serviceUserId) {
|
|
// Create a service impersonation token (use short durations, rotate regularly)
|
|
const serviceClient = await adminPb
|
|
.collection('service_accounts')
|
|
.impersonate(serviceUserId, 86400); // 24 hours max, rotate via scheduled task
|
|
|
|
// Return token for service to use
|
|
return serviceClient.authStore.token;
|
|
}
|
|
|
|
// Using API key token in another service
|
|
async function useApiKey(apiToken) {
|
|
const pb = new PocketBase('http://127.0.0.1:8090');
|
|
|
|
// Manually set the token
|
|
pb.authStore.save(apiToken, null);
|
|
|
|
// Now requests use the service account's permissions
|
|
const data = await pb.collection('data').getList();
|
|
return data;
|
|
}
|
|
```
|
|
|
|
**Important considerations:**
|
|
|
|
```javascript
|
|
// Impersonation tokens are non-renewable
|
|
const client = await adminPb.collection('users').impersonate(userId, 3600);
|
|
|
|
// This will fail - can't refresh impersonation tokens
|
|
try {
|
|
await client.collection('users').authRefresh();
|
|
} catch (error) {
|
|
// Expected: impersonation tokens can't be refreshed
|
|
}
|
|
|
|
// For continuous access, generate new token when needed
|
|
async function getImpersonatedClient(userId) {
|
|
// Check if existing token is still valid
|
|
if (cachedClient?.authStore.isValid) {
|
|
return cachedClient;
|
|
}
|
|
|
|
// Generate fresh token
|
|
return await adminPb.collection('users').impersonate(userId, 3600);
|
|
}
|
|
```
|
|
|
|
**Security best practices:**
|
|
- Use short durations for support tasks
|
|
- Log all impersonation events
|
|
- Restrict impersonation to specific admin roles
|
|
- Never expose impersonation capability in client code
|
|
- Use dedicated service accounts for API keys
|
|
|
|
Reference: [PocketBase Impersonation](https://pocketbase.io/docs/authentication/#impersonate-authentication)
|