From 89f1ad566156c8d882744daace7c885d06036ee7 Mon Sep 17 00:00:00 2001 From: Lukas <38840142+lukasstorck@users.noreply.github.com> Date: Thu, 3 Sep 2026 01:22:11 +0200 Subject: [PATCH 1/3] feature: add oidc login --- compose.yaml | 3 + frontend/Dockerfile | 6 + frontend/src/App.vue | 5 + frontend/src/components/SignInDialog.vue | 26 ++ frontend/src/constants.js | 5 +- frontend/src/utils/sign_in_utils.js | 27 ++ frontend/src/views/SignIn.vue | 31 +- server/.env.template | 8 +- server/models/calendar.go | 1 + server/routes/auth.go | 131 ++++++--- server/services/auth/auth.go | 9 + server/services/auth/oidc.go | 345 +++++++++++++++++++++++ 12 files changed, 546 insertions(+), 51 deletions(-) create mode 100644 server/services/auth/oidc.go diff --git a/compose.yaml b/compose.yaml index 1bf75f785..deb7fb16d 100644 --- a/compose.yaml +++ b/compose.yaml @@ -21,6 +21,9 @@ services: VUE_APP_POSTHOG_API_KEY: ${VUE_APP_POSTHOG_API_KEY:-} VUE_APP_GOOGLE_CLIENT_ID: ${CLIENT_ID:-} VUE_APP_MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID:-} + VUE_APP_OIDC_PROVIDER_NAME: ${OIDC_PROVIDER_NAME:-} + VUE_APP_OIDC_CLIENT_ID: ${OIDC_CLIENT_ID:-} + VUE_APP_OIDC_AUTHORIZATION_ENDPOINT: ${OIDC_ISSUER_URL:-}/authorize volumes: - frontend_dist:/app/dist command: diff --git a/frontend/Dockerfile b/frontend/Dockerfile index c03ed002c..9faae00bd 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -16,10 +16,16 @@ COPY . . ARG VUE_APP_POSTHOG_API_KEY="" ARG VUE_APP_GOOGLE_CLIENT_ID="" ARG VUE_APP_MICROSOFT_CLIENT_ID="" +ARG VUE_APP_OIDC_CLIENT_ID="" +ARG VUE_APP_OIDC_AUTHORIZATION_ENDPOINT="" +ARG VUE_APP_OIDC_PROVIDER_NAME="" ENV VUE_APP_POSTHOG_API_KEY=$VUE_APP_POSTHOG_API_KEY ENV VUE_APP_GOOGLE_CLIENT_ID=$VUE_APP_GOOGLE_CLIENT_ID ENV VUE_APP_MICROSOFT_CLIENT_ID=$VUE_APP_MICROSOFT_CLIENT_ID +ENV VUE_APP_OIDC_CLIENT_ID=$VUE_APP_OIDC_CLIENT_ID +ENV VUE_APP_OIDC_AUTHORIZATION_ENDPOINT=$VUE_APP_OIDC_AUTHORIZATION_ENDPOINT +ENV VUE_APP_OIDC_PROVIDER_NAME=$VUE_APP_OIDC_PROVIDER_NAME # Build the app and move to /build so it's not hidden by volume mount RUN npm run build && mv dist /build diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 176178b5c..91e751372 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -236,6 +236,7 @@ import { isPhone, post, signInGoogle, + signInOidc, signInOutlook, isPremiumUser, } from "@/utils" @@ -391,6 +392,10 @@ export default { state, selectAccount: true, }) + } else if (calendarType === calendarTypes.OIDC) { + signInOidc({ + state, + }) } } }, diff --git a/frontend/src/components/SignInDialog.vue b/frontend/src/components/SignInDialog.vue index 8077809c2..68f0084f0 100644 --- a/frontend/src/components/SignInDialog.vue +++ b/frontend/src/components/SignInDialog.vue @@ -45,6 +45,21 @@ + +
+ mdi-shield-account-outline + + Continue with {{ oidcProviderName }} + +
+
@@ -206,6 +221,17 @@ export default { props: { value: { type: Boolean, required: true }, }, + computed: { + oidcEnabled() { + return !!( + process.env.VUE_APP_OIDC_CLIENT_ID && + process.env.VUE_APP_OIDC_AUTHORIZATION_ENDPOINT + ) + }, + oidcProviderName() { + return process.env.VUE_APP_OIDC_PROVIDER_NAME || "SSO" + }, + }, data() { return { calendarTypes, diff --git a/frontend/src/constants.js b/frontend/src/constants.js index 216d11595..52891ba9b 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -48,7 +48,8 @@ export const calendarTypes = Object.freeze({ GOOGLE: "google", APPLE: "apple", OUTLOOK: "outlook", - ICS: "ics" + ICS: "ics", + OIDC: "oidc", }) export const upgradeDialogTypes = Object.freeze({ @@ -183,4 +184,4 @@ export const guestUserId = "000000000000000000000000" export const numFreeEvents = 3 -export const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/ \ No newline at end of file +export const urlRegex = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/ diff --git a/frontend/src/utils/sign_in_utils.js b/frontend/src/utils/sign_in_utils.js index 6f150bfe1..ce4916195 100644 --- a/frontend/src/utils/sign_in_utils.js +++ b/frontend/src/utils/sign_in_utils.js @@ -69,3 +69,30 @@ export const signInOutlook = ({ const url = `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&response_mode=query&scope=${scope}${stateString}` window.location.href = url } + +/** + * Redirects the user to the configured OIDC provider's authorization + * endpoint to sign in / create an account. This is authentication-only. + * + * Requires VUE_APP_OIDC_CLIENT_ID and VUE_APP_OIDC_AUTHORIZATION_ENDPOINT to + * be set at frontend build time. + */ +export const signInOidc = ({ state = {} } = {}) => { + const clientId = process.env.VUE_APP_OIDC_CLIENT_ID + const authorizationEndpoint = process.env.VUE_APP_OIDC_AUTHORIZATION_ENDPOINT + const redirectUri = encodeURIComponent(`${window.location.origin}/auth`) + + // We put scope on `state` because not every OIDC provider echoes `scope` + // back on the callback. Auth.vue reads `scope ?? state.scope`. + const scope = "openid email profile" + + if (!state) state = {} + state.calendarType = calendarTypes.OIDC + state.scope = scope + const stateString = `&state=${encodeURIComponent(JSON.stringify(state))}` + + const url = `${authorizationEndpoint}?client_id=${clientId}&redirect_uri=${redirectUri}&response_type=code&scope=${encodeURIComponent( + scope, + )}${stateString}` + window.location.href = url +} diff --git a/frontend/src/views/SignIn.vue b/frontend/src/views/SignIn.vue index 6366f72ca..fe11e0957 100644 --- a/frontend/src/views/SignIn.vue +++ b/frontend/src/views/SignIn.vue @@ -66,6 +66,22 @@
+ +
+ mdi-shield-account-outline + + {{ isSignUp ? "Sign up with" : "Continue with" }} + {{ oidcProviderName }} + +
+
@@ -251,7 +267,7 @@