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..ac0957e 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -1,6 +1,6 @@
@@ -45,5 +69,28 @@ {loading ? 'Signing in…' : 'Sign in'} + {#if ssoEnabled} +
or
+ + {/if}
+ + diff --git a/frontend/src/pages/Participants.svelte b/frontend/src/pages/Participants.svelte index 65eda24..0be1485 100644 --- a/frontend/src/pages/Participants.svelte +++ b/frontend/src/pages/Participants.svelte @@ -248,7 +248,7 @@ {#if showAdd && canManage}
-
+
diff --git a/frontend/src/pages/ScheduleBoard.svelte b/frontend/src/pages/ScheduleBoard.svelte index 6bea05e..529264f 100644 --- a/frontend/src/pages/ScheduleBoard.svelte +++ b/frontend/src/pages/ScheduleBoard.svelte @@ -275,7 +275,7 @@ {#if showAdd && canManage}
-
+
- - {#each ($allVolunteers ?? []) - .filter(v => v.department_id === shift.department_id) - .filter(v => !assigned.some(a => a.volunteer.id === v.id)) - as v} - - {/each} - - - - -
- {:else} - + {#if canManage} + {#if assigningShiftID === shift.id} +
+ + + + +
+ {:else} + + {/if} {/if} {/if}
diff --git a/frontend/src/pages/Settings.svelte b/frontend/src/pages/Settings.svelte index 2f6ee8e..9df6b81 100644 --- a/frontend/src/pages/Settings.svelte +++ b/frontend/src/pages/Settings.svelte @@ -7,6 +7,7 @@ let saving = $state(false) let savingEvent = $state(false) let testing = $state(false) + let resetting = $state(false) let error = $state('') let success = $state('') @@ -26,6 +27,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 +52,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 +94,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 @@ -123,7 +131,9 @@ } async function resetModel(label, fn) { + if (resetting) return if (!confirm(`PERMANENTLY DELETE all ${label}? This cannot be undone.`)) return + resetting = true error = '' success = '' try { @@ -131,6 +141,8 @@ success = `Deleted ${result.deleted} ${label}.` } catch (err) { error = err.message + } finally { + resetting = false } } @@ -166,14 +178,14 @@
Loading…
{:else} -
-

Event

-
-
+
+

Event

+
+
-
+
@@ -185,7 +197,7 @@
-
+
@@ -204,11 +216,11 @@
-
-

SMTP Email

+
+

SMTP Email

-
-
+
+
@@ -236,10 +248,27 @@
- +
+

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. +

+
+
+ + +
+
+ + +
+
+
{#if !shiftSignupsOpen} -

+

Opening signups will email all confirmed volunteers their shift signup links.

{/if} @@ -303,24 +332,24 @@
-

Data Management

-

+

Data Management

+

Permanently delete all records of a given type. This cannot be undone.

- - - - -
diff --git a/frontend/src/pages/Users.svelte b/frontend/src/pages/Users.svelte index cb7a3f8..f49c8c6 100644 --- a/frontend/src/pages/Users.svelte +++ b/frontend/src/pages/Users.svelte @@ -149,7 +149,7 @@ {#if showAdd}
-
+
@@ -167,8 +167,8 @@ Roles
{#each availableRoles as r} -