Commit 02af9c04 authored by Grant's avatar Grant
Browse files

bump version

parent de85da26
Loading
Loading
Loading
Loading
+367 −292

File changed.

Preview size limit exceeded, changes collapsed.

+1 −1
Original line number Diff line number Diff line
@@ -21,6 +21,6 @@
  },
  "dependencies": {
    "express": "^4.19.2",
    "openid-client": "^5.6.5"
    "openid-client": "^6.5.0"
  }
}
+36 −46
Original line number Diff line number Diff line
import express from "express";
import { getClient, redirectUri } from "./oidc";
import { TokenSet, errors as OIDC_Errors } from "openid-client";
import { OpenIDController, OpenID } from "./oidc";
import { ResponseBodyError } from "openid-client";

getClient().then(() => {
OpenIDController.initialize().then(() => {
  console.log("OpenID setup");
});

@@ -26,56 +26,46 @@ const renderStatic = (data: { title: string; json: any }) => {
};

app.get("/", async (req, res) => {
  const client = await getClient();
  const client = OpenIDController.get();

  res.redirect(
    client.authorizationUrl({
      prompt: "consent",
      scope: "openid instance",
    })
  );
  res.redirect(client.getAuthorizationURL());
});

app.get("/api/callback", async (req, res) => {
  const client = await getClient();
  let exchange: TokenSet;
  const client = OpenIDController.get();
  let exchange: OpenID.ExchangeToken;

  try {
    const params = client.callbackParams(req);
    exchange = await client.callback(redirectUri, params);
    exchange = await client.exchangeToken(req.originalUrl);
  } catch (e) {
    if (e instanceof OIDC_Errors.RPError) {
      // client error

    if (e instanceof ResponseBodyError) {
      switch (e.error) {
        case "invalid_client":
          // this happens when we're configured with invalid credentials
          console.error(
            "OpenID is improperly configured. Cannot exchange tokens, do I have valid credentials?"
          );
          res.contentType("html").send(
            renderStatic({
          title: "Client Error",
              title: "Configuration Error",
              json: {
            error: e.name,
            message: e.message,
                error: "invalid_client",
              },
            })
          );
          return;
    }

    if (e instanceof OIDC_Errors.OPError) {
      // server error

        case "invalid_grant":
          res.contentType("html").send(
            renderStatic({
          title: "Server Error",
          json: {
            error: e.name,
            data: {
              error: e.error,
              description: e.error_description,
            },
          },
              title: "invalid_grant",
              json: e.cause,
            })
          );
          return;
      }
    }

    console.log(e);

    res.contentType("html").send(
      renderStatic({
@@ -95,7 +85,7 @@ app.get("/api/callback", async (req, res) => {
    );
  }

  const whoami = await client.userinfo<{
  const whoami = await client.userInfo<{
    instance: {
      software: {
        name: string;
@@ -110,7 +100,7 @@ app.get("/api/callback", async (req, res) => {
        name?: string;
      };
    };
  }>(exchange.access_token);
  }>(exchange.access_token, exchange.claims()!.sub);

  console.log(
    new Date().toISOString() + "\t" + whoami.sub + " authenticated successfully"
+71 −16
Original line number Diff line number Diff line
import { BaseClient, Issuer } from "openid-client";
import * as openid from "openid-client";

let issuer: Issuer<BaseClient>;
let client: BaseClient;
const { AUTH_ENDPOINT, AUTH_CLIENT, AUTH_SECRET, AUTH_CALLBACK } = process.env;

export const redirectUri = process.env.AUTH_CALLBACK!;
const HOST =
  new URL(AUTH_CALLBACK!).protocol + "//" + new URL(AUTH_CALLBACK!).host;

export const getClient = async () => {
  if (client) return client;
export namespace OpenID {
  export type ExchangeToken = Awaited<
    ReturnType<typeof openid.authorizationCodeGrant>
  >;
}

  const { AUTH_ENDPOINT, AUTH_CLIENT, AUTH_SECRET } = process.env;
/**
 * Originates from Canvas
 * @see https://sc07.dev/sc07/canvas/-/blob/dc2764d85049ae9c6b890f0b50386a2a36048dd8/packages/server/src/controllers/OpenIDController.ts
 */
export class OpenIDController {
  private static instance: OpenIDController | undefined;
  config: openid.Configuration = {} as any;

  issuer = await Issuer.discover(AUTH_ENDPOINT!);
  client = new issuer.Client({
    client_id: AUTH_CLIENT!,
    client_secret: AUTH_SECRET!,
    response_types: ["code"],
    redirect_uris: [redirectUri],
  });
  private constructor() {}

  return client;
};
  static async initialize() {
    if (typeof OpenIDController.instance !== "undefined") {
      throw new Error(
        "OpenIDController#instance called when already initialized"
      );
    }

    const instance = (OpenIDController.instance = new OpenIDController());

    instance.config = await openid.discovery(
      new URL(AUTH_ENDPOINT!),
      AUTH_CLIENT!,
      {
        client_secret: AUTH_SECRET,
      }
    );
  }

  static get(): OpenIDController {
    if (typeof OpenIDController.instance === "undefined") {
      throw new Error("OpenIDController#get called when not initialized");
    }

    return OpenIDController.instance;
  }

  getRedirectUrl() {
    return AUTH_CALLBACK!;
  }

  getAuthorizationURL() {
    return openid
      .buildAuthorizationUrl(this.config, {
        redirect_uri: this.getRedirectUrl(),
        prompt: "consent",
        scope: "openid instance",
      })
      .toString();
  }

  exchangeToken(relativePath: string) {
    return openid.authorizationCodeGrant(
      this.config,
      new URL(relativePath, HOST)
    );
  }

  userInfo<Data extends object = object>(
    accessToken: string,
    expectedSub: string
  ): Promise<openid.UserInfoResponse & Data> {
    return openid.fetchUserInfo(this.config, accessToken, expectedSub) as any;
  }
}