@j2blasco/ts-auth
Version:
TypeScript authentication abstraction library that eliminates vendor lock-in and provides mock-free testing for both frontend and backend authentication systems
157 lines (156 loc) • 5.64 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthTesting = void 0;
const rxjs_1 = require("rxjs");
const ts_result_1 = require("@j2blasco/ts-result");
/**
* A testing implementation of IAuth for testing purposes.
* This implementation simulates real authentication behavior without external dependencies.
*/
class AuthTesting {
users = new Map();
currentUser = null;
authState = new rxjs_1.BehaviorSubject(undefined);
passwordResetTokens = new Map();
rateLimitTracker = new Map();
idTokens = new Map();
constructor() {
// Initialize with undefined state
this.authState.next(undefined);
}
get authState$() {
return this.authState.asObservable();
}
async signInWithEmailAndPassword(args) {
// Validate email format
if (!this.isValidEmail(args.email)) {
return ts_result_1.resultError.withCode('invalid-email');
}
const user = this.users.get(args.email);
if (!user) {
return ts_result_1.resultError.withCode('user-not-found');
}
if (user.password !== args.password) {
return ts_result_1.resultError.withCode('wrong-password');
}
this.currentUser = { uid: user.uid };
this.authState.next(this.currentUser);
this.idTokens.set(user.uid, `fake-id-token-${user.uid}-${Date.now()}`);
return (0, ts_result_1.resultSuccessVoid)();
}
async getIdToken() {
if (!this.currentUser) {
throw new Error('No user signed in');
}
const token = this.idTokens.get(this.currentUser.uid);
if (!token) {
throw new Error('No token available');
}
return token;
}
async signOut() {
if (this.currentUser) {
this.idTokens.delete(this.currentUser.uid);
}
this.currentUser = null;
this.authState.next(null);
}
async isEmailAvailable(email) {
return !this.users.has(email);
}
async changeEmail(email) {
if (!this.currentUser) {
return ts_result_1.resultError.unknown('No user signed in');
}
if (!(await this.isEmailAvailable(email))) {
return ts_result_1.resultError.withCode('email-not-available');
}
// Find current user and update email
for (const [oldEmail, user] of this.users.entries()) {
if (user.uid === this.currentUser.uid) {
this.users.delete(oldEmail);
this.users.set(email, { ...user, email });
break;
}
}
return (0, ts_result_1.resultSuccess)(undefined);
}
async triggerResetPasswordFlow(email) {
// Check rate limiting
const lastRequest = this.rateLimitTracker.get(email) || 0;
const now = Date.now();
if (now - lastRequest < 60000) {
// 1 minute rate limit
return ts_result_1.resultError.withCode('rate-limit-exceeded');
}
if (!this.users.has(email)) {
return ts_result_1.resultError.withCode('email-not-in-database');
}
// Generate fake token
const token = `fake-reset-token-${Math.random().toString(36).substring(2)}`;
this.passwordResetTokens.set(token, {
token,
email,
expiresAt: now + 3600000, // 1 hour expiry
});
this.rateLimitTracker.set(email, now);
return (0, ts_result_1.resultSuccessVoid)();
}
async requestChangePassword(args) {
const resetToken = this.passwordResetTokens.get(args.passwordToken);
if (!resetToken) {
return ts_result_1.resultError.withCode('token-not-found');
}
if (Date.now() > resetToken.expiresAt) {
this.passwordResetTokens.delete(args.passwordToken);
return ts_result_1.resultError.withCode('token-expired');
}
// Update password
const user = this.users.get(resetToken.email);
if (user) {
this.users.set(resetToken.email, { ...user, password: args.newPassword });
}
// Clean up token
this.passwordResetTokens.delete(args.passwordToken);
return (0, ts_result_1.resultSuccessVoid)();
}
async deleteAccount() {
if (!this.currentUser) {
throw new Error('No user signed in');
}
// Find and remove user
for (const [email, user] of this.users.entries()) {
if (user.uid === this.currentUser.uid) {
this.users.delete(email);
break;
}
}
// Clean up tokens
this.idTokens.delete(this.currentUser.uid);
// Sign out
this.currentUser = null;
this.authState.next(null);
}
async signUp(email, password) {
if (this.users.has(email)) {
throw new Error('Email already in use');
}
const uid = `fake-user-${Math.random().toString(36).substring(2)}`;
this.users.set(email, { uid, email, password });
return uid;
}
// Helper methods for testing
addTestUser(email, password, uid) {
const userId = uid || `fake-user-${Math.random().toString(36).substring(2)}`;
this.users.set(email, { uid: userId, email, password });
return userId;
}
getPasswordResetTokens() {
return Array.from(this.passwordResetTokens.keys());
}
isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}
exports.AuthTesting = AuthTesting;