UNPKG

@nr1e/lucia-adapter-dynamodb

Version:
176 lines 6.31 kB
import { BatchWriteItemCommand, DeleteItemCommand, PutItemCommand, QueryCommand, UpdateItemCommand, GetItemCommand, } from '@aws-sdk/client-dynamodb'; import { marshall, unmarshall } from '@aws-sdk/util-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 DeleteItemCommand({ TableName: this.tableName, Key: { sid: { S: 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) { const expiredSessions = res.Items.map((x) => unmarshall(x)); keys.push(...expiredSessions.map((item) => ({ sid: { S: 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 BatchWriteItemCommand({ 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': { S: userId }, }, Select: 'SPECIFIC_ATTRIBUTES', ProjectionExpression: '#sid', }); } async getSessionAndUser(sessionId) { const sessionRes = await this.client.send(new GetItemCommand({ TableName: this.tableName, Key: { sid: { S: 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': { S: 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 PutItemCommand({ TableName: this.tableName, Item: marshall({ 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 UpdateItemCommand({ TableName: this.tableName, Key: { sid: { S: sessionId }, }, UpdateExpression: 'SET #exp = :exp', ConditionExpression: '#sid = :sid', ExpressionAttributeNames: { '#sid': 'sid', '#exp': 'exp', }, ExpressionAttributeValues: { ':exp': { N: `${expires}` }, ':sid': { S: 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': { S: 'Session' }, ':exp': { N: `${Math.floor(new Date().getTime() / 1000)}` }, }, KeyConditionExpression: '#typ = :typ AND #exp < :exp', Select: 'SPECIFIC_ATTRIBUTES', ProjectionExpression: '#sid', }); } itemToSession(item) { const unmarshalled = unmarshall(item); return { id: unmarshalled.sid, userId: unmarshalled.uid, expiresAt: new Date(unmarshalled.exp * 1000), attributes: unmarshalled.attrs, }; } } //# sourceMappingURL=index.js.map