migrate to OIDC for auth
This commit is contained in:
parent
732feacd5b
commit
8801efc9a3
|
@ -9,7 +9,13 @@ export const User = () => {
|
|||
<div className="user-name">{user.user.username}</div>
|
||||
<div className="user-instance">{user.service.instance.hostname}</div>
|
||||
</div>
|
||||
<img src={user.user.profile} alt="User Avatar" className="user-avatar" />
|
||||
{user.user.picture_url && (
|
||||
<img
|
||||
src={user.user.picture_url}
|
||||
alt="User Avatar"
|
||||
className="user-avatar"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
|
|
|
@ -40,6 +40,8 @@ export interface IAppContext {
|
|||
settingsSidebar: boolean;
|
||||
setSettingsSidebar: (v: boolean) => void;
|
||||
undo?: { available: true; expireAt: number };
|
||||
loadChat: boolean;
|
||||
setLoadChat: (v: boolean) => void;
|
||||
}
|
||||
|
||||
export interface IPalleteContext {
|
||||
|
@ -114,13 +116,19 @@ export type AuthSession = {
|
|||
software: {
|
||||
name: string;
|
||||
version: string;
|
||||
logo_uri?: string;
|
||||
repository?: string;
|
||||
homepage?: string;
|
||||
};
|
||||
instance: {
|
||||
hostname: string;
|
||||
logo_uri?: string;
|
||||
banner_uri?: string;
|
||||
name?: string;
|
||||
};
|
||||
};
|
||||
user: {
|
||||
username: string;
|
||||
profile: string;
|
||||
picture_url?: string;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,44 +1,57 @@
|
|||
import { Router } from "express";
|
||||
import { prisma } from "./lib/prisma";
|
||||
import { OpenID } from "./lib/oidc";
|
||||
|
||||
const app = Router();
|
||||
|
||||
const { AUTH_ENDPOINT, AUTH_CLIENT, AUTH_SECRET } = process.env;
|
||||
|
||||
app.get("/me", (req, res) => {
|
||||
res.json(req.session);
|
||||
});
|
||||
|
||||
app.get("/login", (req, res) => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("service", "canvas");
|
||||
|
||||
res.redirect(AUTH_ENDPOINT + "/login?" + params);
|
||||
res.redirect(
|
||||
OpenID.client.authorizationUrl({
|
||||
prompt: "consent",
|
||||
scope: "openid instance",
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// TODO: logout endpoint
|
||||
|
||||
app.get("/callback", async (req, res) => {
|
||||
const { code } = req.query;
|
||||
// const { code } = req.query;
|
||||
|
||||
const who = await fetch(AUTH_ENDPOINT + "/api/auth/identify", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${AUTH_CLIENT}:${AUTH_SECRET}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
code,
|
||||
}),
|
||||
}).then((a) => a.json());
|
||||
|
||||
if (!who.success) {
|
||||
res.json({
|
||||
error: "AUTHENTICATION FAILED",
|
||||
error_message: who.error || "no error specified",
|
||||
const params = OpenID.client.callbackParams(req);
|
||||
const exchange = await OpenID.client.callback(
|
||||
OpenID.getRedirectUrl(),
|
||||
params
|
||||
);
|
||||
if (!exchange || !exchange.access_token) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: "FAILED TOKEN EXCHANGE",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const [username, hostname] = who.user.sub.split("@");
|
||||
const whoami = await OpenID.client.userinfo<{
|
||||
instance: {
|
||||
software: {
|
||||
name: string;
|
||||
version: string;
|
||||
logo_uri?: string;
|
||||
repository?: string;
|
||||
homepage?: string;
|
||||
};
|
||||
instance: {
|
||||
logo_uri?: string;
|
||||
banner_uri?: string;
|
||||
name?: string;
|
||||
};
|
||||
};
|
||||
}>(exchange.access_token);
|
||||
|
||||
const [username, hostname] = whoami.sub.split("@");
|
||||
|
||||
await prisma.user.upsert({
|
||||
where: {
|
||||
|
@ -52,14 +65,14 @@ app.get("/callback", async (req, res) => {
|
|||
|
||||
req.session.user = {
|
||||
service: {
|
||||
...who.service,
|
||||
...whoami.instance,
|
||||
instance: {
|
||||
...who.service.instance,
|
||||
...whoami.instance.instance,
|
||||
hostname,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
profile: who.user.profile,
|
||||
picture_url: whoami.picture,
|
||||
username,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ import { Redis } from "./lib/redis";
|
|||
import { Logger } from "./lib/Logger";
|
||||
import { ExpressServer } from "./lib/Express";
|
||||
import { SocketServer } from "./lib/SocketServer";
|
||||
import { OpenID } from "./lib/oidc";
|
||||
|
||||
// Validate environment variables
|
||||
|
||||
|
@ -51,7 +52,15 @@ if (!process.env.AUTH_SECRET) {
|
|||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!process.env.OIDC_CALLBACK_HOST) {
|
||||
Logger.error("OIDC_CALLBACK_HOST is not defined");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Redis.connect();
|
||||
OpenID.setup().then(() => {
|
||||
Logger.info("Setup OpenID");
|
||||
});
|
||||
|
||||
const express = new ExpressServer();
|
||||
new SocketServer(express.httpServer);
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { BaseClient, Issuer } from "openid-client";
|
||||
|
||||
class OpenID_ {
|
||||
issuer: Issuer<BaseClient> = {} as any;
|
||||
client: BaseClient = {} as any;
|
||||
|
||||
async setup() {
|
||||
const { AUTH_ENDPOINT, AUTH_CLIENT, AUTH_SECRET } = process.env;
|
||||
|
||||
this.issuer = await Issuer.discover(AUTH_ENDPOINT);
|
||||
this.client = new this.issuer.Client({
|
||||
client_id: AUTH_CLIENT,
|
||||
client_secret: AUTH_SECRET,
|
||||
response_types: ["code"],
|
||||
redirect_uris: [this.getRedirectUrl()],
|
||||
});
|
||||
}
|
||||
|
||||
getRedirectUrl() {
|
||||
return process.env.OIDC_CALLBACK_HOST + "/api/callback";
|
||||
}
|
||||
}
|
||||
|
||||
export const OpenID = new OpenID_();
|
|
@ -25,6 +25,13 @@ declare global {
|
|||
REDIS_HOST: string;
|
||||
REDIS_SESSION_PREFIX: string;
|
||||
|
||||
/**
|
||||
* hostname that is used in the callback
|
||||
*
|
||||
* @example http://localhost:3000
|
||||
* @example https://canvas.com
|
||||
*/
|
||||
OIDC_CALLBACK_HOST: string;
|
||||
/**
|
||||
* If this is set, enable socket.io CORS to this origin
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue