diff --git a/packages/client/src/components/Header/User.tsx b/packages/client/src/components/Header/User.tsx
index 4dd87c2..0df93db 100644
--- a/packages/client/src/components/Header/User.tsx
+++ b/packages/client/src/components/Header/User.tsx
@@ -9,7 +9,13 @@ export const User = () => {
{user.user.username}
{user.service.instance.hostname}
-
+ {user.user.picture_url && (
+
+ )}
) : (
<>>
diff --git a/packages/lib/src/net.ts b/packages/lib/src/net.ts
index 51ddc3b..a17f520 100644
--- a/packages/lib/src/net.ts
+++ b/packages/lib/src/net.ts
@@ -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;
};
};
diff --git a/packages/server/src/api.ts b/packages/server/src/api.ts
index 614e082..4e499ea 100644
--- a/packages/server/src/api.ts
+++ b/packages/server/src/api.ts
@@ -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,
},
};
diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index b3efaf8..7da56d3 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -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);
diff --git a/packages/server/src/lib/oidc.ts b/packages/server/src/lib/oidc.ts
new file mode 100644
index 0000000..cf184ae
--- /dev/null
+++ b/packages/server/src/lib/oidc.ts
@@ -0,0 +1,24 @@
+import { BaseClient, Issuer } from "openid-client";
+
+class OpenID_ {
+ issuer: Issuer = {} 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_();
diff --git a/packages/server/src/types.ts b/packages/server/src/types.ts
index de76328..58aa90a 100644
--- a/packages/server/src/types.ts
+++ b/packages/server/src/types.ts
@@ -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
*