@loopback/security
Version:
A LoopBack component for security support.
201 lines (175 loc) • 5.08 kB
text/typescript
// Copyright IBM Corp. and LoopBack contributors 2019. All Rights Reserved.
// Node module: @loopback/security
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
/**
* A symbol for stringified id of security related objects
*/
export const securityId = Symbol('securityId');
/**
* Represent a user, an application, or a device
*/
export interface Principal {
/**
* Name/id
*/
[securityId]: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[attribute: string]: any;
}
export class TypedPrincipal implements Principal {
constructor(
public readonly principal: Principal,
public readonly type: string,
) {}
get [securityId]() {
return `${this.type}:${this.principal[securityId]}`;
}
}
/**
* The minimum set of attributes that describe a user.
*/
export interface UserProfile extends Principal {
// `email` and `name` are added to be identical with the
// `UserProfile` that previously was exported by `@loopback/authentication`
email?: string;
name?: string;
}
export interface Organization extends Principal {}
export interface Team extends Principal {}
export interface ClientApplication extends Principal {}
export interface Role extends Principal {
name: string;
}
/**
* Security attributes used to authenticate the subject. Such credentials
* include passwords, Kerberos tickets, and public key certificates.
*/
export interface Credential {}
/**
* `Permission` defines an action/access against a protected resource. It's
* the `what` for security.
*
* There are three levels of permissions
*
* - Resource level (Order, User)
* - Instance level (Order-0001, User-1001)
* - Property level (User-0001.email)
*
* @example
* - create a user (action: create, resource type: user)
* - read email of a user (action: read, resource property: user.email)
* - change email of a user (action: update, resource property: user.email)
* - cancel an order (action: delete, resource type: order)
*/
export class Permission {
/**
* Action or access of a protected resources, such as `read`, `create`,
* `update`, or `delete`
*/
action: string;
/**
* Type of protected resource, such as `Order` or `Customer`
*/
resourceType: string;
/**
* Property of a protected resource type/instance, such as `email`
*/
resourceProperty?: string;
/**
* Identity of a protected resource instance, such as `order-0001` or
* `customer-101`
*/
resourceId?: string;
get [securityId]() {
const resIds: string[] = [];
resIds.push(this.resourceType);
if (this.resourceProperty) {
resIds.push(this.resourceProperty);
}
const inst = this.resourceId ? `:${this.resourceId}` : '';
return `${resIds.join('.')}:${this.action}${inst}`;
}
}
/**
* oAuth 2.0 scope
*/
export interface Scope extends Permission {
name: string;
}
/**
* `Subject` represents both security state and operations for a single
* request. It's the `who` for security.
*
* Such operations include:
* - authentication (login)
* - authorization (access control)
* - session access
* - logout
*
*/
export interface Subject {
/**
* An array of principals. It can include information about the current user,
* the client application, and granted authorities.
*
* `Subject` represents both security state and operations for a single
* application user.
*
* Such operations include:
* - authentication (login)
* - authorization (access control)
* - session access
* - logout
*/
principals: Set<TypedPrincipal>;
/**
* An array of credentials, such as password, access token, or private/public
* keys.
*/
credentials: Set<Credential>;
/**
* An array of authorities granted by the user to the client application. One
* example is {@link https://tools.ietf.org/html/rfc6749#section-3.3 | oAuth2 scopes).
*/
authorities: Set<Permission>;
}
/**
* Default implementation of `Subject`
*/
export class DefaultSubject implements Subject {
readonly principals = new Set<TypedPrincipal>();
readonly authorities = new Set<Permission>();
readonly credentials = new Set<Credential>();
addUser(...users: UserProfile[]) {
for (const user of users) {
this.principals.add(new TypedPrincipal(user, 'USER'));
}
}
addApplication(app: ClientApplication) {
this.principals.add(new TypedPrincipal(app, 'APPLICATION'));
}
addAuthority(...authorities: Permission[]) {
for (const authority of authorities) {
this.authorities.add(authority);
}
}
addCredential(...credentials: Credential[]) {
for (const credential of credentials) {
this.credentials.add(credential);
}
}
getPrincipal(type: string) {
let principal: Principal | undefined;
for (const p of this.principals) {
if (p.type === type) {
principal = p.principal;
break;
}
}
return principal;
}
get user() {
return this.getPrincipal('USER') as UserProfile | undefined;
}
}