Custom Repositories

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;
    }
}
ssofyKnowledge Base
At our core, we believe that staying up-to-date with the latest trends and advancements in Authentication and related areas is essential. That's why we take great pride in keeping you informed with the latest insights, updates, and news in this rapidly evolving landscape.


Do you need support?
SSOfy is by Cubelet Ltd.
Copyright © 2024 Cubelet Ltd. All rights reserved.