feat(sdk): add initial browser and server authentication SDK exports
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import * as plugins from './plugins.js';
|
||||
import { IdpSdkAccountDoc, setAccountDocSmartdataDb } from './classes.account-doc.js';
|
||||
import { PasswordHasher } from './classes.password-hasher.js';
|
||||
import type { ICreateIdpSdkAccountOptions, IIdpSdkAccount, TIdpAccountAuthSource } from './interfaces.js';
|
||||
|
||||
export class SmartdataAccountStore {
|
||||
constructor(private optionsArg: { smartdataDb: plugins.smartdata.SmartdataDb }) {
|
||||
setAccountDocSmartdataDb(optionsArg.smartdataDb);
|
||||
}
|
||||
|
||||
public normalizeEmail(emailArg: string): string {
|
||||
return emailArg.trim().toLowerCase();
|
||||
}
|
||||
|
||||
public async createAccount(optionsArg: ICreateIdpSdkAccountOptions): Promise<IIdpSdkAccount> {
|
||||
const emailNormalized = this.normalizeEmail(optionsArg.email);
|
||||
if (!emailNormalized || !emailNormalized.includes('@')) {
|
||||
throw new Error('A valid account email is required');
|
||||
}
|
||||
const existing = await IdpSdkAccountDoc.findByEmailNormalized(emailNormalized);
|
||||
if (existing) {
|
||||
throw new Error(`Account already exists for ${emailNormalized}`);
|
||||
}
|
||||
|
||||
const authSources = this.normalizeAuthSources(optionsArg.authSources);
|
||||
if (authSources.length === 0) {
|
||||
throw new Error('At least one auth source is required');
|
||||
}
|
||||
if (authSources.includes('local') && !optionsArg.password) {
|
||||
throw new Error('A local password is required for local auth');
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const doc = new IdpSdkAccountDoc();
|
||||
doc.id = plugins.crypto.randomUUID();
|
||||
doc.email = optionsArg.email.trim();
|
||||
doc.emailNormalized = emailNormalized;
|
||||
doc.name = optionsArg.name.trim() || doc.email;
|
||||
doc.role = optionsArg.role;
|
||||
doc.status = optionsArg.status || 'active';
|
||||
doc.authSources = authSources;
|
||||
doc.passwordHash = optionsArg.password ? await PasswordHasher.hashPassword(optionsArg.password) : undefined;
|
||||
doc.idpSubject = optionsArg.idpSubject;
|
||||
doc.createdAt = now;
|
||||
doc.updatedAt = now;
|
||||
await doc.save();
|
||||
return doc.toAccount();
|
||||
}
|
||||
|
||||
public async getAccountByEmail(emailArg: string): Promise<IIdpSdkAccount | null> {
|
||||
const doc = await IdpSdkAccountDoc.findByEmailNormalized(this.normalizeEmail(emailArg));
|
||||
return doc?.toAccount() || null;
|
||||
}
|
||||
|
||||
public async getAccountById(idArg: string): Promise<IIdpSdkAccount | null> {
|
||||
const doc = await IdpSdkAccountDoc.findById(idArg);
|
||||
return doc?.toAccount() || null;
|
||||
}
|
||||
|
||||
public async listAccounts(): Promise<IIdpSdkAccount[]> {
|
||||
const docs = await IdpSdkAccountDoc.getInstances({});
|
||||
return docs.map((docArg) => docArg.toAccount());
|
||||
}
|
||||
|
||||
public async hasActiveAdminAccount(): Promise<boolean> {
|
||||
const admins = await IdpSdkAccountDoc.findAdmins();
|
||||
return admins.length > 0;
|
||||
}
|
||||
|
||||
public async verifyLocalPassword(accountArg: IIdpSdkAccount, passwordArg: string): Promise<boolean> {
|
||||
if (accountArg.status !== 'active' || !accountArg.authSources.includes('local')) {
|
||||
return false;
|
||||
}
|
||||
return PasswordHasher.verifyPassword(passwordArg, accountArg.passwordHash);
|
||||
}
|
||||
|
||||
public async updateLoginState(accountIdArg: string, patchArg: { idpSubject?: string }): Promise<IIdpSdkAccount | null> {
|
||||
const doc = await IdpSdkAccountDoc.findById(accountIdArg);
|
||||
if (!doc) {
|
||||
return null;
|
||||
}
|
||||
if (patchArg.idpSubject !== undefined) {
|
||||
doc.idpSubject = patchArg.idpSubject;
|
||||
}
|
||||
doc.lastLoginAt = Date.now();
|
||||
doc.updatedAt = Date.now();
|
||||
await doc.save();
|
||||
return doc.toAccount();
|
||||
}
|
||||
|
||||
private normalizeAuthSources(authSourcesArg: TIdpAccountAuthSource[]): TIdpAccountAuthSource[] {
|
||||
return [...new Set(authSourcesArg.filter((sourceArg) => sourceArg === 'local' || sourceArg === 'idp.global'))];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user