SDK includes repository interfaces for typescript that are standardized to be implemented for custom repository implementations.
Default repositories would work for majority of applications. However, in certain cases, you might need to create your own custom repositories or extend the existing ones in order to meet your requirements.
Following are examples of how to construct repository interfaces for customized implementations:
import { Repositories, Models } from "@ssofy/node-sdk";
...
export class ClientRepository implements Repositories.ClientRepository {
async findById(id: string): Promise<Models.ClientEntity | null> {
let client: Models.ClientEntity | null = null;
// logic to find an OAuth2 Client by id (client_id)
return client;
}
}
import { Repositories, Models } from "@ssofy/node-sdk";
...
export class ScopeRepository implements Repositories.ScopeRepository {
async all(lang: string): Promise<Models.ScopeEntity[]> {
let scopes: Models.ScopeEntity[] = [];
// logic to get list of scopes
return scopes;
}
}
import { Repositories, Models, Helpers } from "@ssofy/node-sdk";
...
export class UserRepository implements Repositories.UserRepository {
protected userTransformer: Transformers.UserTransformer;
constructor(userTransformer: Transformers.UserTransformer) {
this.userTransformer = userTransformer;
}
async find(field: string, value: string, ip?: string): Promise<Models.UserEntity | null> {
let user: Models.UserEntity | null;
// logic to find the user
return this.userTransformer.transform(user);
}
async findById(id: string, ip?: string): Promise<Models.UserEntity | null> {
return this.find('id', id, ip);
}
async findByToken(token: string, ip?: string): Promise<Models.UserEntity | null> {
// logic to find the user by a temporary token
}
async findBySocialLinkOrCreate(provider: string, user: Models.UserEntity, ip?: string): Promise<Models.UserEntity> {
// find the user by the given provider (google, facebook, etc.) and user.id,
// where user.id is the unique id provided by the social provider.
// create if not exists
// make sure the user.id in the returned user model is the internal id and not the provider id
}
async findByEmailOrCreate(user: Models.UserEntity, password?: string, ip?: string): Promise<Models.UserEntity> {
let existingUser: Models.UserEntity | null = this.find('email', user.email, ip);
if (!existingUser) {
return this.create(user, password, ip);
}
return existingUser;
}
async create(user: Models.UserEntity, password?: string, ip?: string): Promise<Models.UserEntity> {
// logic to create the user
}
async update(user: Models.UserEntity, ip?: string): Promise<Models.UserEntity> {
// logic to update the user
}
async createToken(userId: string, ttl?: number): Promise<Models.TokenEntity> {
// create a temporary user token
const token: string = Helpers.randomString(32);
await this.tokenStorage.put(this.tokenStorageKey(token), userId, ttl);
return {
token: token,
ttl: ttl ?? 60,
};
}
async deleteToken(token: string): Promise<void> {
// delete the temporary token
return this.tokenStorage.delete(this.tokenStorageKey(token));
}
async verifyPassword(userId: string, password?: string, ip?: string): Promise<boolean> {
// logic to verify the password
}
async updatePassword(userId: string, password: string, ip?: string): Promise<void> {
// logic to update the password
}
}
import { Repositories, Models } from "@ssofy/node-sdk";
...
export class SocialLinkRepository implements Repositories.SocialLinkRepository {
async getUserId(provider: string, identifier: string): Promise<string | null> {
// get the user id if a previous link was created between the provider and the internal user
}
link(provider: string, identifier: string, userId: string): Promise<void> {
// create a link between the id provided by the social provider and the internal user
}
}
import { Repositories, Models } from "@ssofy/node-sdk";
...
export class OTPRepository implements Repositories.OTPRepository {
async findAllByAction(userId: string, action: string, ip?: string): Promise<Models.OTPOptionEntity[]> {
let options: Models.OTPOptionEntity[] = [];
const otpAction: 'login' | 'password_reset' | 'password_renew' = <'login' | 'password_reset' | 'password_renew'>action;
// logic to get available OTP Options when user requests options for any of OTP actions
return options;
}
async findById(optionId: string, ip?: string): Promise<Models.OTPOptionEntity | null> {
// return the OTP option by id
}
async newVerificationCode(option: Models.OTPOptionEntity, ip?: string): Promise<string> {
// generate and store a new OTP Code
const group = `otp-${option.id}`;
const code = Helpers.randomDigits(6);
const key = this.codeKey(code, group);
await this.codeStorage.put(key, option.user_id, 60 * 60);
return code;
}
async destroyVerificationCode(optionId: string, code: string, ip?: string): Promise<void> {
// delete the OTP code from the storage
const group = `otp-${optionId}`;
const key = this.codeKey(code, group);
return this.codeStorage.delete(key);
}
async verify(optionId: string, code: string, ip?: string): Promise<boolean> {
// verify the OTP Code
const group = `otp-${optionId}`;
const key = this.codeKey(code, group);
const userId = await this.codeStorage.get(key);
if (!userId) {
return false;
}
// mark user's email/phone as verified
const option: Models.OTPOptionEntity | null = await this.findById(optionId);
if (!option) {
return false;
}
const user: Models.UserEntity | null = await this.userRepository.findById(option.user_id, ip);
if (!user) {
return false;
}
// it is a good practice mark the identifier (email / phone number) as verified if the verification was successful
return true;
}
}