🔐 OIDC back-channel logout (#521)

* feat: oidc backchannel session storage
* feat: oidc backchannel logout
* ref: e2e test
* 🛠️ Code review
* ♻️ Do not mock internal auth token but call "/login" with oidc_token instead
* ♻️ Refactoring for back-channel logout test
* fix: feedback and e2e tests
* feat: more e2e tests
* ♻️ Added test to test multiple login requests flow
* ref: jwt sid verifier and e2e tests

---------

Co-authored-by: Monta <monta@HP-ProBook-445-14-inch-G9-Notebook-PC-505aadfc.localdomain>
Co-authored-by: Anton SHEPILOV <ashepilov@linagora.com>
This commit is contained in:
Montassar Ghanmy
2024-05-24 12:52:58 +01:00
committed by GitHub
parent c8a9145906
commit b4412d3ed3
22 changed files with 426 additions and 22 deletions
@@ -23,6 +23,7 @@ import { ConsoleServiceImpl } from "../service";
import coalesce from "../../../utils/coalesce";
import config from "config";
import { CompanyUserRole } from "src/services/user/web/types";
import Session from "../entities/session";
export class ConsoleRemoteClient implements ConsoleServiceClient {
version: "1";
@@ -166,7 +167,6 @@ export class ConsoleRemoteClient implements ConsoleServiceClient {
await gr.services.users.save(user);
//For now TDrive works with only one company as we don't get it from the SSO
let company = await gr.services.companies.getCompany({
id: "00000000-0000-4000-0000-000000000000",
});
@@ -253,4 +253,66 @@ export class ConsoleRemoteClient implements ConsoleServiceClient {
},
};
}
async updateUserSession(idToken: string): Promise<string> {
const sessionInfo = (await this.verifier.verifyIdToken(idToken, this.infos.client_id))?.claims;
// make sure sid claim is present in the token and not empty
if (sessionInfo.sid) {
const sessionRepository = gr.services.console.getSessionRepo();
// check for existing session
const existingSession = await sessionRepository.findOne({
sid: sessionInfo.sid,
});
if (existingSession) {
return existingSession.sid;
} else {
const sessionBody = new Session();
sessionBody.sub = sessionInfo.sub;
sessionBody.sid = sessionInfo.sid;
await sessionRepository.save(sessionBody);
return sessionBody.sid;
}
} else {
throw new CrudException("Missing sid claim", 400);
}
}
async backChannelLogout(logoutToken: string): Promise<void> {
const payload = await this.verifier.verifyLogoutToken(logoutToken);
if (!payload.iss || !payload.aud || !payload.iat || !payload.jti || !payload.events) {
throw new CrudException("Missing required claims", 400);
}
if (payload.nonce) {
throw new CrudException("Nonce claim is prohibited", 400);
}
if (!payload.sub && !payload.sid) {
throw new CrudException("Missing sub or sid claim", 400);
}
const sessionRepository = gr.services.console.getSessionRepo();
const session = await sessionRepository.findOne({ sid: payload.sid });
if (session) {
await sessionRepository.remove(session);
}
}
async verifyJwtSid(sid: string): Promise<void> {
const sessionRepository = gr.services.console.getSessionRepo();
if (sid) {
const session = await sessionRepository.findOne({
sid,
});
if (!session) {
// fail for not matching session id
throw new Error("Invalid session id");
}
} else {
// fail for missing session id
throw new Error("Missing session id");
}
}
}