Add ODIC Back-Channel Logout (#304)

* prevent returning expired sessions

* add issuer to ODIC creds

* get id token in ODIC

* make session signin return session

* working backchannel logout?

* require https for ODIC provider

* handle wellknown not being https

* find session api progress

* fix windows build

* return session token on session

* switch OIDC to #searchSessions

* update pnpm

* switch to using message on error obj

* move odic callback

* fix type errors

* redirect old oidc callback

* make redirect url a URL

* remove scheduled task downloadCleanup

* fix session search for oidc

* fix signin result

* cleanup code

* ignore data dir

* fix lint error
This commit is contained in:
Husky
2026-01-19 17:50:04 -05:00
committed by GitHub
parent 2967e433ca
commit f04daf0388
18 changed files with 710 additions and 115 deletions
+52 -6
View File
@@ -1,5 +1,5 @@
import cacheHandler from "../cache";
import type { Session, SessionProvider } from "./types";
import type { SessionProvider, SessionWithToken } from "./types";
/**
* DO NOT USE THIS. THE CACHE EVICTS SESSIONS.
@@ -7,19 +7,24 @@ import type { Session, SessionProvider } from "./types";
* This needs work. TODO.
*/
export default function createCacheSessionProvider() {
const sessions = cacheHandler.createCache<Session>("cacheSessionProvider");
const sessions = cacheHandler.createCache<SessionWithToken>(
"cacheSessionProvider",
);
const memoryProvider: SessionProvider = {
async setSession(token, data) {
await sessions.set(token, data);
return true;
const session = { ...data, token };
await sessions.set(token, session);
return session;
},
async getSession<T extends Session>(token: string): Promise<T | undefined> {
async getSession<T extends SessionWithToken>(
token: string,
): Promise<T | undefined> {
const session = await sessions.get(token);
return session ? (session as T) : undefined; // Ensure undefined is returned if session is not found
},
async updateSession(token, data) {
return await this.setSession(token, data);
return (await this.setSession(token, data)) !== undefined;
},
async removeSession(token) {
await sessions.remove(token);
@@ -34,6 +39,47 @@ export default function createCacheSessionProvider() {
if (session.expiresAt < now) await this.removeSession(token);
}
},
async findSessions(options) {
const results: SessionWithToken[] = [];
for (const token of await sessions.getKeys()) {
const session = await sessions.get(token);
if (!session) continue;
let match = true;
if (
options.userId &&
session.authenticated &&
session.authenticated.userId !== options.userId
) {
match = false;
}
if (options.oidc && session.oidc) {
for (const [key, value] of Object.entries(options.oidc)) {
// stringify to do deep comparison
if (
JSON.stringify(
(session.oidc as unknown as Record<string, unknown>)[key],
) !== JSON.stringify(value)
) {
match = false;
break;
}
}
}
for (const [key, value] of Object.entries(options.data || {})) {
// stringify to do deep comparison
if (JSON.stringify(session.data[key]) !== JSON.stringify(value)) {
match = false;
break;
}
}
if (match) {
results.push(session);
}
}
return results;
},
};
return memoryProvider;