Install: @travetto/auth-model
npm install @travetto/auth-model
# or
yarn add @travetto/auth-model
This module supports the integration between the Authentication module and the Data Modeling Support.
The asset module requires a CRUD-model to provide functionality for reading and storing user information. You can use any existing providers to serve as your CRUD, or you can roll your own.
Install: provider
npm install @travetto/model-{provider}
# or
yarn add @travetto/model-{provider}
Currently, the following are packages that provide CRUD:
- DynamoDB Model Support - @travetto/model-dynamodb
- Elasticsearch Model Source - @travetto/model-elasticsearch
- Firestore Model Support - @travetto/model-firestore
- MongoDB Model Support - @travetto/model-mongo
- Redis Model Support - @travetto/model-redis
- S3 Model Support - @travetto/model-s3
- MySQL Model Service - @travetto/model-mysql
- PostgreSQL Model Service - @travetto/model-postgres
- SQLite Model Service - @travetto/model-sqlite
- Memory Model Support - @travetto/model-memory
- File Model Support - @travetto/model-file The module itself is fairly straightforward, and truly the only integration point for this module to work is defined at the model level. The contract for authentication is established in code as providing translation to and from a Registered Principal.
A registered principal extends the base concept of an principal, by adding in additional fields needed for local registration, specifically password management information.
Code: Registered Principal
export interface RegisteredPrincipal extends Principal {
/**
* Password hash
*/
hash?: string;
/**
* Password salt
*/
salt?: string;
/**
* Temporary Reset Token
*/
resetToken?: string;
/**
* End date for the reset token
*/
resetExpires?: Date;
/**
* The actual password, only used on password set/update
*/
password?: string;
}
Code: A valid user model
import { Model } from '@travetto/model';
import { RegisteredPrincipal } from '@travetto/auth-model';
@Model()
export class User implements RegisteredPrincipal {
id: string;
source: string;
details: Record<string, unknown>;
password?: string;
salt: string;
hash: string;
resetToken?: string;
resetExpires?: Date;
permissions: string[];
}
Additionally, there exists a common practice of mapping various external security principals into a local contract. These external identities, as provided from countless authentication schemes, need to be homogenized for use. This has been handled in other frameworks by using external configuration, and creating a mapping between the two set of fields. Within this module, the mappings are defined as functions in which you can translate to the model from an identity or to an identity from a model.
Code: Principal Source configuration
import { InjectableFactory } from '@travetto/di';
import { ModelAuthService, RegisteredPrincipal } from '@travetto/auth-model';
import { ModelCrudSupport } from '@travetto/model';
import { User } from './model';
class AuthConfig {
@InjectableFactory()
static getModelAuthService(svc: ModelCrudSupport) {
return new ModelAuthService(
svc,
User,
(u: User) => ({ // This converts User to a RegisteredPrincipal
source: 'model',
provider: 'model',
id: u.id,
permissions: u.permissions,
hash: u.hash,
salt: u.salt,
resetToken: u.resetToken,
resetExpires: u.resetExpires,
password: u.password,
details: u,
}),
(u: Partial<RegisteredPrincipal>) => User.from(({ // This converts a RegisteredPrincipal to a User
id: u.id,
permissions: [...(u.permissions || [])],
hash: u.hash,
salt: u.salt,
resetToken: u.resetToken,
resetExpires: u.resetExpires,
})
)
);
}
}
Code: Sample usage
import { AppError } from '@travetto/runtime';
import { Injectable, Inject } from '@travetto/di';
import { ModelAuthService } from '@travetto/auth-model';
import { User } from './model';
@Injectable()
class UserService {
@Inject()
private auth: ModelAuthService<User>;
async authenticate(identity: User) {
try {
return await this.auth.authenticate(identity);
} catch (err) {
if (err instanceof AppError && err.category === 'notfound') {
return await this.auth.register(identity);
} else {
throw err;
}
}
}
}