From 54da04763f35a6752588ea71e450268fe0c4a41b Mon Sep 17 00:00:00 2001 From: Pen Anderson Date: Tue, 10 Mar 2026 17:45:38 -0500 Subject: [PATCH] Added optional Discourse SSO. --- db.go | 26 ++++ frontend/src/App.svelte | 32 ++++- frontend/src/api.js | 4 + frontend/src/pages/Login.svelte | 49 +++++++- frontend/src/pages/Settings.svelte | 27 +++- handle_settings.go | 14 ++- handle_sso.go | 190 +++++++++++++++++++++++++++++ main.go | 3 + 8 files changed, 337 insertions(+), 8 deletions(-) create mode 100644 handle_sso.go diff --git a/db.go b/db.go index 0315da8..0ec6716 100644 --- a/db.go +++ b/db.go @@ -140,6 +140,11 @@ func migrate(db *sql.DB) error { department_id INTEGER NOT NULL REFERENCES departments(id) ON DELETE CASCADE, PRIMARY KEY (participant_id, department_id) ); + + CREATE TABLE IF NOT EXISTS sso_nonces ( + nonce TEXT PRIMARY KEY, + created_at TEXT NOT NULL DEFAULT (datetime('now')) + ); `) return err } @@ -1350,6 +1355,27 @@ func (app *App) listOpenShiftsForDept(deptID int) ([]Shift, error) { ORDER BY s.day, s.position, s.start_time`, deptID) } +// --- SSO Nonces --- + +func (app *App) createSSONonce(nonce string) error { + _, err := app.db.Exec(`INSERT INTO sso_nonces (nonce) VALUES (?)`, nonce) + return err +} + +func (app *App) consumeSSONonce(nonce string) (bool, error) { + res, err := app.db.Exec( + `DELETE FROM sso_nonces WHERE nonce = ? AND created_at > datetime('now', '-10 minutes')`, nonce) + if err != nil { + return false, err + } + n, _ := res.RowsAffected() + return n > 0, nil +} + +func (app *App) cleanExpiredNonces() { + app.db.Exec(`DELETE FROM sso_nonces WHERE created_at < datetime('now', '-10 minutes')`) +} + // --- Helpers --- func now() string { diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index a1fa253..f680143 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,6 +1,6 @@
@@ -45,5 +67,28 @@ {loading ? 'Signing in…' : 'Sign in'} + {#if ssoEnabled} +
or
+ + {/if}
+ + diff --git a/frontend/src/pages/Settings.svelte b/frontend/src/pages/Settings.svelte index 2f6ee8e..bc87738 100644 --- a/frontend/src/pages/Settings.svelte +++ b/frontend/src/pages/Settings.svelte @@ -26,6 +26,8 @@ let eventEndDate = $state('') let eventTimezone = $state('') const timezones = Intl.supportedValuesOf('timeZone') + let discourseSSOUrl = $state('') + let discourseSSOSecret = $state('') let shiftSignupsOpen = $state(false) let togglingSignups = $state(false) @@ -49,6 +51,8 @@ baseURL = s.base_url ?? '' noteLabel = s.volunteer_note_label ?? 'Additional note' noteRequired = s.volunteer_note_required ?? false + discourseSSOUrl = s.discourse_sso_url ?? '' + discourseSSOSecret = '' shiftSignupsOpen = s.shift_signups_open ?? false } catch (err) { error = err.message @@ -89,14 +93,17 @@ smtp_host: smtpHost, smtp_port: smtpPort, smtp_user: smtpUser, - smtp_password: smtpPassword, // empty = keep existing + smtp_password: smtpPassword, smtp_from: smtpFrom, smtp_from_name: smtpFromName, base_url: baseURL, volunteer_note_label: noteLabel, volunteer_note_required: noteRequired, + discourse_sso_url: discourseSSOUrl, + discourse_sso_secret: discourseSSOSecret, }) smtpPassword = '' + discourseSSOSecret = '' success = 'Settings saved.' } catch (err) { error = err.message @@ -240,6 +247,24 @@ + +

Discourse SSO

+

+ Enable DiscourseConnect SSO so users can log in with their Discourse account. + Set the same secret in your Discourse admin under Connect > discourse connect secret. +

+
+
+ + +
+
+ + +
+
+