Skip to content

Commit 8c55e1b

Browse files
imnotjameskrokosik
andauthored
feat: add support for OIDC auth (#171)
* feat: add support for OIDC auth * Add changes lost in rebase * Add OIDC_ALLOW_DANGEROUS_EMAIL_LINKING * Add callback info to readme * Small env example adjustment * Formatting fix * Add settable icons * Add more provider icons --------- Co-authored-by: krokosik <[email protected]>
1 parent 90a61a9 commit 8c55e1b

File tree

5 files changed

+77
-27
lines changed

5 files changed

+77
-27
lines changed

.env.example

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,20 @@ AUTHENTIK_ID=
5656
AUTHENTIK_SECRET=
5757
AUTHENTIK_ISSUER=
5858

59+
# OIDC Provider
60+
# The (lowercase) name will be used to generate an id and possibly display an icon if it is added in https://github.com/oss-apps/split-pro/blob/main/src/pages/auth/signin.tsx#L25
61+
# If your provider is not added, simpleicon probably has it and you may submit a PR
62+
OIDC_NAME=
63+
OIDC_CLIENT_ID=
64+
OIDC_CLIENT_SECRET=
65+
66+
# An OIDC Well-Known URI registry: https://openid.net/specs/openid-connect-discovery-1_0.html#WellKnownRegistry
67+
# For example, https://example.com/.well-known/openid-configuration
68+
OIDC_WELL_KNOWN_URL=
69+
70+
# Required for some providers to link with existing accounts, make sure you trust your provider to properly verify email addresses
71+
# OIDC_ALLOW_DANGEROUS_EMAIL_LINKING=1
72+
5973
# Storage: any S3 compatible storage will work, for self hosting can use minio
6074
# If you're using minio for dev, you can generate access keys from the console http://localhost:9001/access-keys/new-account
6175
# R2_ACCESS_KEY="access-key"

docker/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,7 @@ docker exec -t <postgres container name> pg_dumpall -c -U postgres > splitpro_ba
8080
```bash
8181
cat splitpro_backup.sql | docker exec -i <postgres container name> psql -U postgres
8282
```
83+
84+
## Authentication
85+
86+
We use NextAuth for authentication providers, offering email, Google and Authentik providers out of the box. Other providers can be customized as OIDC providers, but remember that an OIDC callback is needed. Set up yout `NEXTAUTH_URL` and then a callback address of `https://${NEXTAUTH_URL}/api/auth/callback/oidc`.

src/env.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ export const env = createEnv({
5151
FEEDBACK_EMAIL: z.string().optional(),
5252
DISCORD_WEBHOOK_URL: z.string().optional(),
5353
DEFAULT_HOMEPAGE: z.string().default('/home'),
54+
OIDC_NAME: z.string().optional(),
55+
OIDC_CLIENT_ID: z.string().optional(),
56+
OIDC_CLIENT_SECRET: z.string().optional(),
57+
OIDC_WELL_KNOWN_URL: z.string().optional(),
58+
OIDC_ALLOW_DANGEROUS_EMAIL_LINKING: z.boolean().optional(),
5459
},
5560

5661
/**
@@ -96,6 +101,11 @@ export const env = createEnv({
96101
FEEDBACK_EMAIL: process.env.FEEDBACK_EMAIL,
97102
DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL,
98103
DEFAULT_HOMEPAGE: process.env.DEFAULT_HOMEPAGE,
104+
OIDC_NAME: process.env.OIDC_NAME,
105+
OIDC_CLIENT_ID: process.env.OIDC_CLIENT_ID,
106+
OIDC_CLIENT_SECRET: process.env.OIDC_CLIENT_SECRET,
107+
OIDC_WELL_KNOWN_URL: process.env.OIDC_WELL_KNOWN_URL,
108+
OIDC_ALLOW_DANGEROUS_EMAIL_LINKING: !!process.env.OIDC_ALLOW_DANGEROUS_EMAIL_LINKING,
99109
},
100110
/**
101111
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially

src/pages/auth/signin.tsx

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,22 @@ import { env } from '~/env';
2121
import { getServerAuthSession } from '~/server/auth';
2222
import { customServerSideTranslations } from '~/utils/i18n/server';
2323
import VerificationStep from './VerificationStep';
24+
import {
25+
SiAuth0,
26+
SiAuthelia,
27+
SiAuthentik,
28+
SiGithub,
29+
SiGoogle,
30+
SiKeycloak,
31+
} from '@icons-pack/react-simple-icons';
2432

2533
const providerSvgs = {
26-
github: (
27-
<svg
28-
xmlns="http://www.w3.org/2000/svg"
29-
viewBox="0 0 496 512"
30-
className="fill-primary-foreground h-4 w-4"
31-
>
32-
<path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z" />
33-
</svg>
34-
),
35-
google: (
36-
<svg
37-
xmlns="http://www.w3.org/2000/svg"
38-
viewBox="0 0 488 512"
39-
className="fill-primary-foreground h-4 w-4"
40-
>
41-
<path d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z" />
42-
</svg>
43-
),
44-
authentik: (
45-
<svg
46-
xmlns="http://www.w3.org/2000/svg"
47-
viewBox="0 0 25 25"
48-
className="fill-primary-foreground h-4 w-4"
49-
>
50-
<path d="M13.96 9.01h-0.84V7.492h-1.234v3.663H5.722c0.34 0.517 0.538 0.982 0.538 1.152 0 0.46 -1.445 3.059 -3.197 3.059C0.8 15.427 -0.745 12.8 0.372 10.855a3.062 3.062 0 0 1 2.691 -1.606c1.04 0 1.971 0.915 2.557 1.755V6.577a3.773 3.773 0 0 1 3.77 -3.769h10.84C22.31 2.808 24 4.5 24 6.577v10.845a3.773 3.773 0 0 1 -3.77 3.769h-1.6V17.5h-7.64v3.692h-1.6a3.773 3.773 0 0 1 -3.77 -3.769v-3.41h12.114v-6.52h-1.59v0.893h-0.84v-0.893H13.96v1.516Zm-9.956 1.845c-0.662 -0.703 -1.578 -0.544 -2.209 0 -2.105 2.054 1.338 5.553 3.302 1.447a5.395 5.395 0 0 0 -1.093 -1.447Z" />
51-
</svg>
52-
),
34+
github: <SiGithub />,
35+
google: <SiGoogle />,
36+
authentik: <SiAuthentik />,
37+
authelia: <SiAuthelia />,
38+
auth0: <SiAuth0 />,
39+
keycloak: <SiKeycloak />,
5340
};
5441

5542
const emailSchema = (t: TFunction) =>

src/server/auth.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PrismaAdapter } from '@next-auth/prisma-adapter';
22
import { type GetServerSidePropsContext } from 'next';
3+
import type { User } from 'next-auth';
34
import { type DefaultSession, type NextAuthOptions, getServerSession } from 'next-auth';
45
import { type Adapter, type AdapterUser } from 'next-auth/adapters';
56
import AuthentikProvider from 'next-auth/providers/authentik';
@@ -11,6 +12,7 @@ import { db } from '~/server/db';
1112

1213
import { sendSignUpEmail } from './mailer';
1314
import { getBaseUrl } from '~/utils/api';
15+
import type { OAuthConfig } from 'next-auth/providers/oauth';
1416

1517
/**
1618
* Module augmentation for `next-auth` types. Allows us to add custom properties to the `session`
@@ -195,6 +197,39 @@ function getProviders() {
195197
);
196198
}
197199

200+
if (env.OIDC_CLIENT_ID && env.OIDC_CLIENT_SECRET && env.OIDC_WELL_KNOWN_URL) {
201+
providersList.push({
202+
id: env.OIDC_NAME?.toLowerCase() ?? 'oidc',
203+
name: env.OIDC_NAME ?? 'OIDC',
204+
clientId: env.OIDC_CLIENT_ID,
205+
clientSecret: env.OIDC_CLIENT_SECRET,
206+
type: 'oauth',
207+
wellKnown: env.OIDC_WELL_KNOWN_URL,
208+
authorization: { params: { scope: 'openid email profile' } },
209+
allowDangerousEmailAccountLinking: env.OIDC_ALLOW_DANGEROUS_EMAIL_LINKING,
210+
idToken: true,
211+
profile(profile) {
212+
// This function expects a "standard" next-auth user but we override
213+
// what a next-auth user is above. The expected next-auth user must be
214+
// a record that has an id, a name, an email, and an image.
215+
//
216+
// To work around this, we case to unknown and then `User`.
217+
return {
218+
id: profile.sub,
219+
name: profile.name,
220+
email: profile.email,
221+
image: profile.picture,
222+
} as unknown as User;
223+
},
224+
} satisfies OAuthConfig<{
225+
sub: string;
226+
name: string;
227+
email: string;
228+
picture: string;
229+
preferred_username: string;
230+
}>);
231+
}
232+
198233
return providersList;
199234
}
200235

0 commit comments

Comments
 (0)