Initial commit
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
---
|
||||
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)
|
||||
Reference in New Issue
Block a user