Created
August 3, 2022 13:56
-
-
Save tom-sherman/da5960341765bf50401321b8757e0051 to your computer and use it in GitHub Desktop.
Remix session storage implementation using DynamoDB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as crypto from "node:crypto"; | |
import type { | |
SessionIdStorageStrategy, | |
SessionStorage, | |
} from "@remix-run/server-runtime"; | |
import { createSessionStorage } from "@remix-run/node"; | |
import { DynamoDB } from "aws-sdk"; | |
interface DynamoDbSessionStorageOptions { | |
/** | |
* The Cookie used to store the session id on the client, or options used | |
* to automatically create one. | |
*/ | |
cookie?: SessionIdStorageStrategy["cookie"]; | |
/** | |
* The table used to store sessions. | |
*/ | |
tableName: string; | |
/** | |
* The name of the DynamoDB attribute used to store the session ID. | |
* This should be the table's partition key. | |
*/ | |
idx: string; | |
/** | |
* The name of the DynamoDB attribute used to store the expiration time. | |
* If absent, then no TTL will be stored and session records will not expire. | |
*/ | |
ttl?: string; | |
} | |
export function createDynamoDbSessionStorage({ | |
cookie, | |
tableName, | |
idx, | |
ttl, | |
}: DynamoDbSessionStorageOptions): SessionStorage { | |
const docClient = new DynamoDB.DocumentClient(); | |
const getTtl = (expires: Date | undefined | null) => | |
ttl | |
? expires | |
? Math.round(expires.getTime() / 1000) | |
: undefined | |
: undefined; | |
return createSessionStorage({ | |
cookie, | |
async createData(data, expires) { | |
while (true) { | |
let randomBytes = crypto.randomBytes(8); | |
// This storage manages an id space of 2^64 ids, which is far greater | |
// than the maximum number of files allowed on an NTFS or ext4 volume | |
// (2^32). However, the larger id space should help to avoid collisions | |
// with existing ids when creating new sessions, which speeds things up. | |
let id = [...randomBytes] | |
.map((x) => x.toString(16).padStart(2, "0")) | |
.join(""); | |
const result = await docClient | |
.get({ | |
TableName: tableName, | |
Key: { | |
[idx]: id, | |
}, | |
}) | |
.promise(); | |
if (result.Item) { | |
continue; | |
} | |
await docClient | |
.put({ | |
TableName: tableName, | |
Item: { | |
[idx]: id, | |
...data, | |
ttl: getTtl(expires), | |
}, | |
}) | |
.promise(); | |
return id; | |
} | |
}, | |
async readData(id) { | |
let result = await docClient | |
.get({ | |
TableName: tableName, | |
Key: { [idx]: id }, | |
}) | |
.promise(); | |
const data = result.Item; | |
if (data) { | |
delete data[idx]; | |
if (ttl) delete data[ttl]; | |
} | |
return data ?? null; | |
}, | |
async updateData(id, data, expires) { | |
await docClient | |
.put({ | |
TableName: tableName, | |
Item: { | |
[idx]: id, | |
...data, | |
ttl: getTtl(expires), | |
}, | |
}) | |
.promise(); | |
}, | |
async deleteData(id) { | |
await docClient | |
.delete({ TableName: tableName, Key: { [idx]: id } }) | |
.promise(); | |
}, | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment