mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Added auth/permissions nest concepts
This commit is contained in:
parent
f8555100bc
commit
61a33fdff7
3 changed files with 134 additions and 0 deletions
|
@ -0,0 +1,8 @@
|
|||
import {Reflector} from '@nestjs/core';
|
||||
|
||||
type UserRole = 'Contributor' | 'Author' | 'Editor' | 'Admin' | 'Owner';
|
||||
type APIKeyRole = 'Admin Integration' | 'Ghost Explore Integration' | 'Self-Serve Migration Integration' | 'DB Backup Integration' | 'Scheduler Integration';
|
||||
|
||||
export type Role = UserRole | APIKeyRole;
|
||||
|
||||
export const Roles = Reflector.createDecorator<Role[]>();
|
100
ghost/ghost/src/http/guards/admin-api-authentication.guard.ts
Normal file
100
ghost/ghost/src/http/guards/admin-api-authentication.guard.ts
Normal file
|
@ -0,0 +1,100 @@
|
|||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Inject
|
||||
} from '@nestjs/common';
|
||||
import {Request, Response} from 'express';
|
||||
import {Actor} from '../../common/types/actor.type';
|
||||
import ObjectID from 'bson-objectid';
|
||||
|
||||
// Here we extend the express Request interface with our new type
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Express {
|
||||
// eslint-disable-next-line no-shadow
|
||||
export interface Request {
|
||||
actor?: Actor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface SessionService {
|
||||
// We use any because we've not got types for bookshelf models
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
getUserForSession(req: Request, res: Response): Promise<any>
|
||||
}
|
||||
|
||||
interface AuthenticationService {
|
||||
// We use any because we've not got types for bookshelf models
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
authenticateWithToken(url: string, token: string, ignoreMaxAge: boolean): Promise<any>
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class AdminAPIAuthentication implements CanActivate {
|
||||
constructor(
|
||||
@Inject('SessionService') private sessionService: SessionService,
|
||||
@Inject('AdminAuthenticationService') private authService: AuthenticationService
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
const response = context.switchToHttp().getResponse<Response>();
|
||||
|
||||
const user = await this.sessionService.getUserForSession(request, response);
|
||||
|
||||
if (user) {
|
||||
await this.setUserActor(user, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!request.headers || !request.headers.authorization) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const [scheme, token] = request.headers.authorization.split(' ');
|
||||
|
||||
if (!/^Ghost$/i.test(scheme)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {apiKey, user: apiUser} = await this.authService.authenticateWithToken(
|
||||
request.originalUrl,
|
||||
token,
|
||||
false
|
||||
);
|
||||
|
||||
if (user) {
|
||||
await this.setUserActor(apiUser, request);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (apiKey) {
|
||||
await apiKey.related('role').fetch();
|
||||
const json = apiKey.toJSON();
|
||||
request.actor = {
|
||||
id: ObjectID.createFromHexString(json.integration.id),
|
||||
role: json.role.name,
|
||||
type: 'api_key'
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// This is `any` because again it represents a bookshelf model
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private async setUserActor(user: any, request: Request) {
|
||||
await user.related('roles').fetch();
|
||||
const json = user.toJSON();
|
||||
request.actor = {
|
||||
// BS To work around Owner id === 1
|
||||
id: ObjectID.createFromHexString(json.id === '1' ? 'abcd12345678901234567890' : json.id),
|
||||
role: json.roles[0].name,
|
||||
type: 'user'
|
||||
};
|
||||
}
|
||||
}
|
26
ghost/ghost/src/http/guards/permissions.guard.ts
Normal file
26
ghost/ghost/src/http/guards/permissions.guard.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Inject
|
||||
} from '@nestjs/common';
|
||||
import {Reflector} from '@nestjs/core';
|
||||
import {Roles} from '../../common/decorators/permissions.decorator';
|
||||
import {Request} from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class PermissionsGuard implements CanActivate {
|
||||
constructor(@Inject(Reflector) private reflector: Reflector) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const roles = this.reflector.get(Roles, context.getHandler());
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
|
||||
const role = request.actor?.role;
|
||||
|
||||
if (role && roles.includes(role)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue