codalware-auth
Version:
Complete authentication system with enterprise security, attack protection, team workspaces, waitlist, billing, UI components, 2FA, and account recovery - production-ready in 5 minutes. Enhanced CLI with verification, rollback, and App Router scaffolding.
63 lines (58 loc) • 4.8 kB
text/typescript
import { Adapter, User, Session, MagicToken } from './types';
import * as sdk from 'node-appwrite';
export function createAppwriteAdapter(opts: { client?: unknown; endpoint?: string; project?: string; apiKey?: string; usersCollectionId?: string; tokensCollectionId?: string; sessionsCollectionId?: string }) : Adapter {
const client = (opts.client as any) ?? new (sdk as any).Client().setEndpoint(opts.endpoint || process.env.APPWRITE_ENDPOINT).setProject(opts.project || process.env.APPWRITE_PROJECT).setKey(opts.apiKey || process.env.APPWRITE_KEY);
const databases = new sdk.Databases(client);
const databaseId = process.env.APPWRITE_DATABASE ?? opts.project ?? 'default';
const usersCollection = opts.usersCollectionId ?? process.env.APPWRITE_USERS_COLLECTION ?? 'users';
const tokensCollection = opts.tokensCollectionId ?? process.env.APPWRITE_TOKENS_COLLECTION ?? 'tokens';
const sessionsCollection = opts.sessionsCollectionId ?? process.env.APPWRITE_SESSIONS_COLLECTION ?? 'sessions';
function mapUser(doc: any): User {
return { id: doc.$id, email: doc.email, name: doc.name ?? null, metadata: doc.metadata ?? null, createdAt: new Date(doc.$createdAt), updatedAt: new Date(doc.$updatedAt) };
}
return {
async createUser({ email, name, metadata }) {
const doc = await databases.createDocument(databaseId, usersCollection, sdk.ID.unique(), { email, name, metadata });
return mapUser(doc);
},
async getUserById(id) {
try { const doc = await databases.getDocument(databaseId, usersCollection, id); return mapUser(doc); } catch { return null; }
},
async getUserByEmail(email) {
const res = await databases.listDocuments(databaseId, usersCollection, [sdk.Query.equal('email', email)]);
const doc = res.documents[0];
if (!doc) return null;
return mapUser(doc);
},
async updateUser(id, patch) {
const data: any = {};
if (patch.email !== undefined) data.email = patch.email;
if (patch.name !== undefined) data.name = patch.name;
if (patch.metadata !== undefined) data.metadata = patch.metadata;
const doc = await databases.updateDocument(databaseId, usersCollection, id, data);
return mapUser(doc);
},
async createSession(session) {
const doc = await databases.createDocument(databaseId, sessionsCollection, sdk.ID.unique(), { userId: session.userId, expiresAt: session.expiresAt.toISOString(), metadata: session.metadata });
return { id: doc.$id, userId: doc.userId, createdAt: new Date(doc.$createdAt), expiresAt: new Date(doc.expiresAt), handle: doc.handle ?? null, metadata: doc.metadata ?? null } as Session;
},
async getSessionById(id) {
try { const doc = await databases.getDocument(databaseId, sessionsCollection, id); return { id: doc.$id, userId: doc.userId, createdAt: new Date(doc.$createdAt), expiresAt: new Date(doc.expiresAt), handle: doc.handle ?? null, metadata: doc.metadata ?? null } as Session } catch { return null }
},
async deleteSession(id) { try { await databases.deleteDocument(databaseId, sessionsCollection, id); } catch { /* ignore */ } },
async deleteSessionsByUserId(userId) { const res = await databases.listDocuments(databaseId, sessionsCollection, [sdk.Query.equal('userId', userId)]); for (const d of res.documents) { await databases.deleteDocument(databaseId, sessionsCollection, d.$id); } },
async storeMagicToken({ tokenHash, userId = null, expiresAt, ip = null, userAgent = null }) {
const doc = await databases.createDocument(databaseId, tokensCollection, sdk.ID.unique(), { tokenHash, userId, expiresAt: expiresAt.toISOString(), ip, userAgent });
return { id: doc.$id, tokenHash: doc.tokenHash, userId: doc.userId ?? null, createdAt: new Date(doc.$createdAt), expiresAt: new Date(doc.expiresAt), consumedAt: doc.consumedAt ? new Date(doc.consumedAt) : null, ip: doc.ip ?? null, userAgent: doc.userAgent ?? null } as MagicToken;
},
async findValidMagicToken(tokenHash) {
const res = await databases.listDocuments(databaseId, tokensCollection, [sdk.Query.equal('tokenHash', tokenHash), sdk.Query.isNull('consumedAt')]);
const d = res.documents[0];
if (!d) return null;
const expires = new Date(d.expiresAt);
if (expires <= new Date()) return null;
return { id: d.$id, tokenHash: d.tokenHash, userId: d.userId ?? null, createdAt: new Date(d.$createdAt), expiresAt: new Date(d.expiresAt), consumedAt: d.consumedAt ? new Date(d.consumedAt) : null, ip: d.ip ?? null, userAgent: d.userAgent ?? null } as MagicToken;
},
async consumeMagicToken(id) { try { await databases.updateDocument(databaseId, tokensCollection, id, { consumedAt: new Date().toISOString() }); } catch { /* ignore */ } },
};
}