Skip to content

Instantly share code, notes, and snippets.

@tom-sherman
Created August 3, 2022 13:56
Show Gist options
  • Save tom-sherman/da5960341765bf50401321b8757e0051 to your computer and use it in GitHub Desktop.
Save tom-sherman/da5960341765bf50401321b8757e0051 to your computer and use it in GitHub Desktop.
Remix session storage implementation using DynamoDB
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