alpha
Login
or
Join now
danabra.mov
/
sidetrail
Star
1
Fork
1
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
an app to share curated trails
sidetrail.app
Star
1
Fork
1
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
move oauth caches to redis
author
Dan Abramov
date
6 months ago
(Dec 7, 2025, 1:44 AM +0900)
commit
4398879e
4398879ec4ae2a1f03aaba8d939437e806de003d
parent
f28cb795
f28cb795b0948ccb80ceb1b4c68eedf956fa467f
+78
-13
2 changed files
Expand all
Collapse all
Unified
Split
auth
client.ts
redis-store.ts
+15
-13
auth/client.ts
Reviewed
···
6
6
JoseKey,
7
7
} from "@atproto/oauth-client-node";
8
8
import {
9
9
-
AuthorizationServerMetadataCache,
10
10
-
ProtectedResourceMetadataCache,
11
11
-
} from "@atproto/oauth-client";
12
12
-
import { SimpleStoreMemory } from "@atproto-labs/simple-store-memory";
9
9
+
OAuthAuthorizationServerMetadata,
10
10
+
OAuthProtectedResourceMetadata,
11
11
+
} from "@atproto/oauth-types";
13
12
import { StateStore, SessionStore, initAuthTables, requestLock } from "./storage";
13
13
+
import { SimpleStoreRedis } from "./redis-store";
14
14
15
15
-
// Cache OAuth server metadata for 24 hours (default is 60 seconds)
16
16
-
// This metadata rarely changes - Bluesky would announce OAuth infrastructure changes
17
17
-
const authServerMetadataCache: AuthorizationServerMetadataCache = new SimpleStoreMemory({
18
18
-
ttl: 24 * 60 * 60 * 1000,
19
19
-
max: 100,
15
15
+
const authServerMetadataCache = new SimpleStoreRedis<OAuthAuthorizationServerMetadata>({
16
16
+
keyPrefix: "oauth:as-metadata:",
17
17
+
ttlSeconds: 24 * 60 * 60,
18
18
+
});
19
19
+
const protectedResourceMetadataCache = new SimpleStoreRedis<OAuthProtectedResourceMetadata>({
20
20
+
keyPrefix: "oauth:pr-metadata:",
21
21
+
ttlSeconds: 24 * 60 * 60,
20
22
});
21
21
-
const protectedResourceMetadataCache: ProtectedResourceMetadataCache = new SimpleStoreMemory({
22
22
-
ttl: 24 * 60 * 60 * 1000,
23
23
-
max: 100,
23
23
+
const dpopNonceCache = new SimpleStoreRedis<string>({
24
24
+
keyPrefix: "oauth:dpop-nonce:",
25
25
+
ttlSeconds: 24 * 60 * 60,
24
26
});
25
27
26
28
// Environment variables
···
114
116
stateStore: new StateStore(),
115
117
sessionStore: new SessionStore(),
116
118
requestLock,
117
117
-
// Use longer-lived cache for OAuth metadata (rarely changes)
118
119
authorizationServerMetadataCache: authServerMetadataCache,
119
120
protectedResourceMetadataCache,
121
121
+
dpopNonceCache,
120
122
});
121
123
}
122
124
+63
auth/redis-store.ts
Reviewed
···
1
1
+
import "server-only";
2
2
+
import Redis from "ioredis";
3
3
+
4
4
+
let redis: Redis | null = null;
5
5
+
6
6
+
function getRedis(): Redis {
7
7
+
if (!redis) {
8
8
+
redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379", {
9
9
+
maxRetriesPerRequest: 3,
10
10
+
lazyConnect: true,
11
11
+
});
12
12
+
redis.on("error", (err) => {
13
13
+
console.error("[redis] connection error:", err.message);
14
14
+
});
15
15
+
}
16
16
+
return redis;
17
17
+
}
18
18
+
19
19
+
export type SimpleStoreRedisOptions = {
20
20
+
keyPrefix: string;
21
21
+
ttlSeconds: number;
22
22
+
};
23
23
+
24
24
+
/**
25
25
+
* Simple Redis-backed store compatible with @atproto SimpleStore interface.
26
26
+
* Used for OAuth caches that should survive server restarts.
27
27
+
*/
28
28
+
export class SimpleStoreRedis<V> {
29
29
+
private keyPrefix: string;
30
30
+
private ttlSeconds: number;
31
31
+
32
32
+
constructor(options: SimpleStoreRedisOptions) {
33
33
+
this.keyPrefix = options.keyPrefix;
34
34
+
this.ttlSeconds = options.ttlSeconds;
35
35
+
}
36
36
+
37
37
+
async get(key: string): Promise<V | undefined> {
38
38
+
try {
39
39
+
const value = await getRedis().get(this.keyPrefix + key);
40
40
+
if (!value) return undefined;
41
41
+
return JSON.parse(value) as V;
42
42
+
} catch (err) {
43
43
+
console.error("[redis-store] get error:", err);
44
44
+
return undefined;
45
45
+
}
46
46
+
}
47
47
+
48
48
+
async set(key: string, value: V): Promise<void> {
49
49
+
try {
50
50
+
await getRedis().set(this.keyPrefix + key, JSON.stringify(value), "EX", this.ttlSeconds);
51
51
+
} catch (err) {
52
52
+
console.error("[redis-store] set error:", err);
53
53
+
}
54
54
+
}
55
55
+
56
56
+
async del(key: string): Promise<void> {
57
57
+
try {
58
58
+
await getRedis().del(this.keyPrefix + key);
59
59
+
} catch (err) {
60
60
+
console.error("[redis-store] del error:", err);
61
61
+
}
62
62
+
}
63
63
+
}