UNPKG

@nr1e/lucia-adapter-dynamodb

Version:

A DynamoDB adapter for lucia-auth

173 lines 5.96 kB
import { BatchWriteCommand, DeleteCommand, PutCommand, QueryCommand, UpdateCommand, GetCommand, } from '@aws-sdk/lib-dynamodb'; const MAX_BATCH_SIZE = 25; /** * Adapter using two GSIs */ export class DynamoDBAdapter { client; tableName = 'LuciaAuthTable'; getUser; consistentRead; constructor(options) { this.client = options.client; if (options.tableName) this.tableName = options.tableName; this.getUser = options.getUser; this.consistentRead = options?.consistentRead ?? false; } async deleteSession(sessionId) { await this.client.send(new DeleteCommand({ TableName: this.tableName, Key: { sid: sessionId, }, })); } async deleteSessions(commandInput) { let _lastEvaluatedKey = undefined; const keys = []; // query keys do { if (_lastEvaluatedKey) commandInput.ExclusiveStartKey = _lastEvaluatedKey; const res = await this.client.send(new QueryCommand(commandInput)); if (res?.Items?.length) { keys.push(...res.Items.map((item) => ({ sid: item.sid, }))); } _lastEvaluatedKey = res?.LastEvaluatedKey; // delete all keys for (let i = 0; i < keys.length; i += MAX_BATCH_SIZE) { const batch = keys.slice(i, i + MAX_BATCH_SIZE); await this.client.send(new BatchWriteCommand({ RequestItems: { [this.tableName]: batch.map((key) => ({ DeleteRequest: { Key: key }, })), }, })); } } while (_lastEvaluatedKey); } async deleteUserSessions(userId) { await this.deleteSessions({ TableName: this.tableName, IndexName: 'Gs1', KeyConditionExpression: '#uid = :uid', ExpressionAttributeNames: { '#uid': 'uid', '#sid': 'sid', }, ExpressionAttributeValues: { ':uid': userId, }, Select: 'SPECIFIC_ATTRIBUTES', ProjectionExpression: '#sid', }); } async getSessionAndUser(sessionId) { const sessionRes = await this.client.send(new GetCommand({ TableName: this.tableName, Key: { sid: sessionId, }, ConsistentRead: this.consistentRead, })); if (!sessionRes.Item) return [null, null]; const session = this.itemToSession(sessionRes.Item); const user = await this.getUser(session.userId, this.client); return [session, user]; } async getUserSessions(userId) { const sessions = []; let _lastEvaluatedKey = undefined; do { const commandInput = { TableName: this.tableName, IndexName: 'Gs1', ExpressionAttributeNames: { '#uid': 'uid', }, ExpressionAttributeValues: { ':uid': userId, }, KeyConditionExpression: '#uid = :uid', }; if (_lastEvaluatedKey) commandInput.ExclusiveStartKey = _lastEvaluatedKey; const res = await this.client.send(new QueryCommand(commandInput)); if (res?.Items?.length) { sessions.push(...res.Items.map((x) => this.itemToSession(x))); } _lastEvaluatedKey = res?.LastEvaluatedKey; } while (_lastEvaluatedKey); return sessions; } async setSession(databaseSession) { const expires = Math.floor(databaseSession.expiresAt.getTime() / 1000); await this.client.send(new PutCommand({ TableName: this.tableName, Item: { sid: databaseSession.id, uid: databaseSession.userId, exp: expires, typ: 'Session', attrs: databaseSession.attributes, }, })); } async updateSessionExpiration(sessionId, expiresAt) { if (expiresAt.getTime() > Date.now()) { const expires = Math.floor(expiresAt.getTime() / 1000); // update the session await this.client.send(new UpdateCommand({ TableName: this.tableName, Key: { sid: sessionId, }, UpdateExpression: 'SET #exp = :exp', ConditionExpression: '#sid = :sid', ExpressionAttributeNames: { '#sid': 'sid', '#exp': 'exp', }, ExpressionAttributeValues: { ':exp': expires, ':sid': sessionId, }, })); } else { await this.deleteSession(sessionId); } } async deleteExpiredSessions() { await this.deleteSessions({ TableName: this.tableName, IndexName: 'Gs2', ExpressionAttributeNames: { '#sid': 'sid', '#exp': 'exp', '#typ': 'typ', }, ExpressionAttributeValues: { ':typ': 'Session', ':exp': Math.floor(new Date().getTime() / 1000), }, KeyConditionExpression: '#typ = :typ AND #exp < :exp', Select: 'SPECIFIC_ATTRIBUTES', ProjectionExpression: '#sid', }); } itemToSession(item) { return { id: item.sid, userId: item.uid, expiresAt: new Date(item.exp * 1000), attributes: item.attrs, }; } } //# sourceMappingURL=index.js.map