Commit 8801efc9 authored by Grant's avatar Grant
Browse files

migrate to OIDC for auth

parent 732feacd
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -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>
  ) : (
    <></>
+9 −1
Original line number Diff line number Diff line
@@ -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;
  };
};
+40 −27
Original line number Diff line number Diff line
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",
    })
  );
});

app.get("/callback", async (req, res) => {
  const { code } = req.query;
// TODO: logout endpoint

  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());
app.get("/callback", async (req, res) => {
  // const { code } = req.query;

  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,
    },
  };
+9 −0
Original line number Diff line number Diff line
@@ -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);
+24 −0
Original line number Diff line number Diff line
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_();
Loading