mirror of
https://github.com/LogicLabs-OU/OpenArchiver.git
synced 2026-04-06 00:31:57 +02:00
IAM policies
This commit is contained in:
@@ -18,7 +18,37 @@ A policy is a JSON object that consists of one or more statements. Each statemen
|
||||
- **`Action`**: A list of operations that the policy grants or denies permission to perform. Actions are formatted as `service:operation`.
|
||||
- **`Resource`**: A list of resources to which the actions apply. Resources are specified in a hierarchical format. Wildcards (`*`) can be used.
|
||||
|
||||
## 2. Actions and Resources by Service
|
||||
## 2. Wildcard Support
|
||||
|
||||
Our IAM system supports wildcards (`*`) in both `Action` and `Resource` fields to provide flexible permission management, as defined in the `PolicyValidator`.
|
||||
|
||||
### Action Wildcards
|
||||
|
||||
You can use wildcards to grant broad permissions for actions:
|
||||
|
||||
- **Global Wildcard (`*`)**: A standalone `*` in the `Action` field grants permission for all possible actions across all services.
|
||||
```json
|
||||
"Action": ["*"]
|
||||
```
|
||||
- **Service-Level Wildcard (`service:*`)**: A wildcard at the end of an action string grants permission for all actions within that specific service.
|
||||
```json
|
||||
"Action": ["archive:*"]
|
||||
```
|
||||
|
||||
### Resource Wildcards
|
||||
|
||||
Wildcards can also be used to specify resources:
|
||||
|
||||
- **Global Wildcard (`*`)**: A standalone `*` in the `Resource` field applies the policy to all resources in the system.
|
||||
```json
|
||||
"Resource": ["*"]
|
||||
```
|
||||
- **Partial Wildcards**: Some services allow wildcards at specific points in the resource path to refer to all resources of a certain type. For example, to target all ingestion sources:
|
||||
```json
|
||||
"Resource": ["ingestion-source/*"]
|
||||
```
|
||||
|
||||
## 3. Actions and Resources by Service
|
||||
|
||||
The following sections define the available actions and resources, categorized by their respective services.
|
||||
|
||||
@@ -36,12 +66,12 @@ The `archive` service pertains to all actions related to accessing and managing
|
||||
|
||||
**Resources:**
|
||||
|
||||
| Resource | Description |
|
||||
| :------------------------------------ | :------------------------------------------------------------- |
|
||||
| `archive/all` | Represents the entire email archive. |
|
||||
| `archive/ingestion-source/{sourceId}` | Scopes the action to emails from a specific ingestion source. |
|
||||
| `archive/email/{emailId}` | Scopes the action to a single, specific email. |
|
||||
| `archive/custodian/{custodianId}` | Scopes the action to emails belonging to a specific custodian. |
|
||||
| Resource | Description |
|
||||
| :------------------------------------ | :--------------------------------------------------------------------------------------- |
|
||||
| `archive/all` | Represents the entire email archive. |
|
||||
| `archive/ingestion-source/{sourceId}` | Scopes the action to emails from a specific ingestion source. |
|
||||
| `archive/mailbox/{email}` | Scopes the action to a single, specific mailbox, usually identified by an email address. |
|
||||
| `archive/custodian/{custodianId}` | Scopes the action to emails belonging to a specific custodian. |
|
||||
|
||||
---
|
||||
|
||||
@@ -13,7 +13,12 @@ export class AuthController {
|
||||
this.#authService = authService;
|
||||
this.#userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only used for setting up the instance, should only be displayed once upon instance set up.
|
||||
* @param req
|
||||
* @param res
|
||||
* @returns
|
||||
*/
|
||||
public setup = async (req: Request, res: Response): Promise<Response> => {
|
||||
const { email, password, first_name, last_name } = req.body;
|
||||
|
||||
@@ -29,9 +34,8 @@ export class AuthController {
|
||||
return res.status(403).json({ message: 'Setup has already been completed.' });
|
||||
}
|
||||
|
||||
const newUser = await this.#userService.createUser({ email, password, first_name, last_name });
|
||||
const newUser = await this.#userService.createAdminUser({ email, password, first_name, last_name });
|
||||
const result = await this.#authService.login(email, password);
|
||||
|
||||
return res.status(201).json(result);
|
||||
} catch (error) {
|
||||
console.error('Setup error:', error);
|
||||
|
||||
@@ -32,6 +32,5 @@ export const createIamRouter = (iamController: IamController): Router => {
|
||||
* @access Private
|
||||
*/
|
||||
router.delete('/roles/:id', requireAuth, iamController.deleteRole);
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ const ARCHIVE_ACTIONS = {
|
||||
const ARCHIVE_RESOURCES = {
|
||||
ALL: 'archive/all',
|
||||
INGESTION_SOURCE: 'archive/ingestion-source/*',
|
||||
EMAIL: 'archive/email/*',
|
||||
MAILBOX: 'archive/mailbox/*',
|
||||
CUSTODIAN: 'archive/custodian/*',
|
||||
} as const;
|
||||
|
||||
@@ -113,43 +113,8 @@ export const ValidActions: Set<string> = new Set([
|
||||
* as is `archive/email/123-abc`.
|
||||
*/
|
||||
export const ValidResourcePatterns = {
|
||||
archive: /^archive\/(all|ingestion-source\/[^\/]+|email\/[^\/]+|custodian\/[^\/]+)$/,
|
||||
archive: /^archive\/(all|ingestion-source\/[^\/]+|mailbox\/[^\/]+|custodian\/[^\/]+)$/,
|
||||
ingestion: /^ingestion-source\/(\*|[^\/]+)$/,
|
||||
system: /^system\/(settings|users|user\/[^\/]+)$/,
|
||||
dashboard: /^dashboard\/\*$/,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* --- How to Use These Definitions for Validation (Conceptual) ---
|
||||
*
|
||||
* A validator function would be created, likely in an `AuthorizationService`,
|
||||
* that accepts a `PolicyStatement` object.
|
||||
*
|
||||
* export function isPolicyStatementValid(statement: PolicyStatement): boolean {
|
||||
* // 1. Validate Actions
|
||||
* for (const action of statement.Action) {
|
||||
* if (action.endsWith('*')) {
|
||||
* // For wildcards, check if the service prefix is valid
|
||||
* const service = action.split(':')[0];
|
||||
* if (!Object.keys(ValidResourcePatterns).includes(service)) {
|
||||
* return false; // Invalid service
|
||||
* }
|
||||
* } else if (!ValidActions.has(action)) {
|
||||
* return false; // Action is not in the set of known actions
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // 2. Validate Resources
|
||||
* for (const resource of statement.Resource) {
|
||||
* const service = resource.split('/')[0];
|
||||
* const pattern = ValidResourcePatterns[service];
|
||||
*
|
||||
* if (!pattern || !pattern.test(resource)) {
|
||||
* return false; // Resource format is invalid for the specified service
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* return true;
|
||||
* }
|
||||
*/
|
||||
|
||||
@@ -35,7 +35,7 @@ const {
|
||||
|
||||
|
||||
if (!PORT_BACKEND || !JWT_SECRET || !JWT_EXPIRES_IN) {
|
||||
throw new Error('Missing required environment variables for the backend.');
|
||||
throw new Error('Missing required environment variables for the backend: PORT_BACKEND, JWT_SECRET, JWT_EXPIRES_IN.');
|
||||
}
|
||||
|
||||
// --- Dependency Injection Setup ---
|
||||
|
||||
@@ -36,7 +36,7 @@ export class UserService {
|
||||
* @param userDetails The details of the user to create.
|
||||
* @returns The newly created user object.
|
||||
*/
|
||||
public async createUser(userDetails: Pick<User, 'email' | 'first_name' | 'last_name'> & { password?: string; }): Promise<(typeof schema.users.$inferSelect)> {
|
||||
public async createAdminUser(userDetails: Pick<User, 'email' | 'first_name' | 'last_name'> & { password?: string; }): Promise<(typeof schema.users.$inferSelect)> {
|
||||
const { email, first_name, last_name, password } = userDetails;
|
||||
|
||||
const userCountResult = await db.select({ count: sql<number>`count(*)` }).from(schema.users);
|
||||
@@ -51,29 +51,29 @@ export class UserService {
|
||||
password: hashedPassword,
|
||||
}).returning();
|
||||
|
||||
if (isFirstUser) {
|
||||
let superAdminRole = await db.query.roles.findFirst({
|
||||
where: eq(schema.roles.name, 'Super Admin')
|
||||
});
|
||||
// find super admin role
|
||||
let superAdminRole = await db.query.roles.findFirst({
|
||||
where: eq(schema.roles.name, 'Super Admin')
|
||||
});
|
||||
|
||||
if (!superAdminRole) {
|
||||
const suerAdminPolicies: PolicyStatement[] = [{
|
||||
Effect: 'Allow',
|
||||
Action: ['*'],
|
||||
Resource: ['*']
|
||||
}];
|
||||
superAdminRole = (await db.insert(schema.roles).values({
|
||||
name: 'Super Admin',
|
||||
policies: suerAdminPolicies
|
||||
}).returning())[0];
|
||||
}
|
||||
|
||||
await db.insert(schema.userRoles).values({
|
||||
userId: newUser[0].id,
|
||||
roleId: superAdminRole.id
|
||||
});
|
||||
if (!superAdminRole) {
|
||||
const suerAdminPolicies: PolicyStatement[] = [{
|
||||
Effect: 'Allow',
|
||||
Action: ['*'],
|
||||
Resource: ['*']
|
||||
}];
|
||||
superAdminRole = (await db.insert(schema.roles).values({
|
||||
name: 'Super Admin',
|
||||
policies: suerAdminPolicies
|
||||
}).returning())[0];
|
||||
}
|
||||
|
||||
await db.insert(schema.userRoles).values({
|
||||
userId: newUser[0].id,
|
||||
roleId: superAdminRole.id
|
||||
});
|
||||
|
||||
|
||||
return newUser[0];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user