MCPcopy
hub / github.com/codeaashu/claude-code / OAuthAdapter

Class OAuthAdapter

src/server/web/auth/oauth-auth.ts:79–276  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

77 * ADMIN_USERS — comma-separated user IDs or emails that get admin role
78 */
79export class OAuthAdapter implements AuthAdapter {
80 private readonly store: SessionStore;
81 private readonly clientId: string;
82 private readonly clientSecret: string;
83 private readonly issuer: string;
84 private readonly callbackUrl: string;
85 private readonly scopes: string;
86 private readonly adminUsers: ReadonlySet<string>;
87 /** Cached OIDC discovery document. */
88 private discovery: OIDCDiscovery | null = null;
89 /** In-flight state tokens to prevent CSRF. */
90 private readonly pendingStates = new Map<string, number>();
91
92 constructor(store: SessionStore) {
93 this.store = store;
94 this.clientId = process.env.OAUTH_CLIENT_ID ?? "";
95 this.clientSecret = process.env.OAUTH_CLIENT_SECRET ?? "";
96 this.issuer = (process.env.OAUTH_ISSUER ?? "").replace(/\/$/, "");
97 this.callbackUrl =
98 process.env.OAUTH_CALLBACK_URL ?? "http://localhost:3000/auth/callback";
99 this.scopes = process.env.OAUTH_SCOPES ?? "openid email profile";
100 this.adminUsers = new Set(
101 (process.env.ADMIN_USERS ?? "")
102 .split(",")
103 .map((s) => s.trim())
104 .filter(Boolean),
105 );
106
107 // Periodically prune stale state tokens (>10 min old).
108 setInterval(() => {
109 const cutoff = Date.now() - 10 * 60_000;
110 for (const [s, t] of this.pendingStates) {
111 if (t < cutoff) this.pendingStates.delete(s);
112 }
113 }, 5 * 60_000).unref();
114 }
115
116 authenticate(req: IncomingMessage): AuthUser | null {
117 const session = this.store.getFromRequest(req);
118 if (!session) return null;
119 return {
120 id: session.userId,
121 email: session.email,
122 name: session.name,
123 isAdmin:
124 session.isAdmin ||
125 this.adminUsers.has(session.userId) ||
126 (session.email ? this.adminUsers.has(session.email) : false),
127 };
128 }
129
130 setupRoutes(app: Application): void {
131 // GET /auth/login — redirect to the identity provider
132 app.get("/auth/login", async (_req, res) => {
133 try {
134 const disc = await this.getDiscovery();
135 const state = randomBytes(16).toString("hex");
136 this.pendingStates.set(state, Date.now());

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected