mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-31 23:19:15 +02:00
⚡️ Add support for custom claim paths (#1197)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
parent
1d138cb2ab
commit
104d214d2e
7 changed files with 90 additions and 17 deletions
|
@ -7,12 +7,8 @@ description: How to use your own identity provider
|
|||
<Info>Available in v3.4.0 and later.</Info>
|
||||
|
||||
<Warning>
|
||||
#### Account Linking
|
||||
|
||||
Accounts using the same email are linked together. This assumes
|
||||
that you are using a trusted identity provider that uses verified email
|
||||
addresses.
|
||||
|
||||
Accounts using the same email are linked together. This assumes that you are
|
||||
using a trusted identity provider that uses verified email addresses.
|
||||
</Warning>
|
||||
|
||||
## OpenID Connect (OIDC)
|
||||
|
@ -29,7 +25,7 @@ Your OAuth 2.0 application needs to be configured with the following scopes:
|
|||
|
||||
### Callback URL / Redirect URI
|
||||
|
||||
Your identity provider will redirect the user back to the following URL:
|
||||
Your identity provider should redirect the user back to the following URL:
|
||||
|
||||
```
|
||||
{BASE_URL}/api/auth/callback/oidc
|
||||
|
@ -46,7 +42,7 @@ The following configuration options are available for OIDC.
|
|||
All required fields must be set for OIDC to be enabled.
|
||||
|
||||
<ParamField path="OIDC_NAME" default="OpenID Connect">
|
||||
The user-facing name of your provider as it will be shown on the login page
|
||||
The display name of your provider as it will be shown on the login page
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="OIDC_DISCOVERY_URL" required>
|
||||
|
@ -60,3 +56,17 @@ All required fields must be set for OIDC to be enabled.
|
|||
<ParamField path="OIDC_CLIENT_SECRET" required>
|
||||
The client secret of your OIDC application
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="OIDC_NAME_CLAIM_PATH" default="name">
|
||||
The path to the claim that contains the user's name
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="OIDC_EMAIL_CLAIM_PATH" default="email">
|
||||
The path to the claim that contains the user's email address
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="OIDC_PICTURE_CLAIM_PATH" default="picture">
|
||||
The path to the claim that contains the user's profile picture
|
||||
</ParamField>
|
||||
|
||||
<Info>Use dot notation in `_CLAIM_PATH` fields to access nested objects.</Info>
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"extends": "@rallly/tsconfig/next.json",
|
||||
"include": ["**/*.ts", "**/*.tsx", "**/*.js"],
|
||||
"exclude": ["node_modules"],
|
||||
}
|
|
@ -19,6 +19,9 @@ export const env = createEnv({
|
|||
OIDC_DISCOVERY_URL: z.string().optional(),
|
||||
OIDC_CLIENT_ID: z.string().optional(),
|
||||
OIDC_CLIENT_SECRET: z.string().optional(),
|
||||
OIDC_EMAIL_CLAIM_PATH: z.string().default("email"),
|
||||
OIDC_NAME_CLAIM_PATH: z.string().default("name"),
|
||||
OIDC_PICTURE_CLAIM_PATH: z.string().default("picture"),
|
||||
/**
|
||||
* Email Provider
|
||||
* Choose which service provider to use for sending emails.
|
||||
|
@ -70,6 +73,9 @@ export const env = createEnv({
|
|||
OIDC_DISCOVERY_URL: process.env.OIDC_DISCOVERY_URL,
|
||||
OIDC_CLIENT_ID: process.env.OIDC_CLIENT_ID,
|
||||
OIDC_CLIENT_SECRET: process.env.OIDC_CLIENT_SECRET,
|
||||
OIDC_EMAIL_CLAIM_PATH: process.env.OIDC_EMAIL_CLAIM_PATH,
|
||||
OIDC_NAME_CLAIM_PATH: process.env.OIDC_NAME_CLAIM_PATH,
|
||||
OIDC_PICTURE_CLAIM_PATH: process.env.OIDC_PICTURE_CLAIM_PATH,
|
||||
EMAIL_PROVIDER: process.env.EMAIL_PROVIDER,
|
||||
SMTP_HOST: process.env.SMTP_HOST,
|
||||
SMTP_USER: process.env.SMTP_USER,
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
NextApiRequest,
|
||||
NextApiResponse,
|
||||
} from "next";
|
||||
import { NextAuthOptions } from "next-auth";
|
||||
import { NextAuthOptions, User } from "next-auth";
|
||||
import NextAuth, {
|
||||
getServerSession as getServerSessionWithOptions,
|
||||
} from "next-auth/next";
|
||||
|
@ -18,10 +18,12 @@ import GoogleProvider from "next-auth/providers/google";
|
|||
import { Provider } from "next-auth/providers/index";
|
||||
|
||||
import { posthog } from "@/app/posthog";
|
||||
import { env } from "@/env";
|
||||
import { absoluteUrl } from "@/utils/absolute-url";
|
||||
import { CustomPrismaAdapter } from "@/utils/auth/custom-prisma-adapter";
|
||||
import { mergeGuestsIntoUser } from "@/utils/auth/merge-user";
|
||||
import { emailClient } from "@/utils/emails";
|
||||
import { getValueByPath } from "@/utils/get-value-by-path";
|
||||
|
||||
const providers: Provider[] = [
|
||||
// When a user registers, we don't want to go through the email verification process
|
||||
|
@ -128,9 +130,10 @@ if (
|
|||
profile(profile) {
|
||||
return {
|
||||
id: profile.sub,
|
||||
name: profile.name,
|
||||
email: profile.email,
|
||||
};
|
||||
name: getValueByPath(profile, env.OIDC_NAME_CLAIM_PATH),
|
||||
email: getValueByPath(profile, env.OIDC_EMAIL_CLAIM_PATH),
|
||||
image: getValueByPath(profile, env.OIDC_PICTURE_CLAIM_PATH),
|
||||
} as User;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
41
apps/web/src/utils/get-value-by-path.test.ts
Normal file
41
apps/web/src/utils/get-value-by-path.test.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getValueByPath } from "./get-value-by-path";
|
||||
|
||||
describe("getValueByPath", () => {
|
||||
describe("when the path is not nested", () => {
|
||||
it("should return the value of the key", () => {
|
||||
const path = "key";
|
||||
const obj = { key: "value" };
|
||||
const value = getValueByPath(obj, path);
|
||||
expect(value).toBe("value");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the path is nested", () => {
|
||||
it("should return the value of the nested key", () => {
|
||||
const path = "nested.key";
|
||||
const obj = { nested: { key: "value" } };
|
||||
const value = getValueByPath(obj, path);
|
||||
expect(value).toBe("value");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the path is deeply nested", () => {
|
||||
it("should return the value of the deeply nested key", () => {
|
||||
const path = "deeply.nested.key";
|
||||
const obj = { deeply: { nested: { key: "value" } } };
|
||||
const value = getValueByPath(obj, path);
|
||||
expect(value).toBe("value");
|
||||
});
|
||||
});
|
||||
|
||||
describe("when the path is not found", () => {
|
||||
it("should return undefined", () => {
|
||||
const path = "key";
|
||||
const obj = {};
|
||||
const value = getValueByPath(obj, path);
|
||||
expect(value).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
15
apps/web/src/utils/get-value-by-path.ts
Normal file
15
apps/web/src/utils/get-value-by-path.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export function getValueByPath<O extends Record<string, unknown>>(
|
||||
obj: O,
|
||||
path: string,
|
||||
): unknown {
|
||||
const pathArray = path.split(".");
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let curr: any = obj;
|
||||
for (const part of pathArray) {
|
||||
if (curr[part] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
curr = curr[part];
|
||||
}
|
||||
return curr;
|
||||
}
|
|
@ -84,7 +84,10 @@
|
|||
"OIDC_CLIENT_ID",
|
||||
"OIDC_CLIENT_SECRET",
|
||||
"OIDC_DISCOVERY_URL",
|
||||
"OIDC_EMAIL_CLAIM_PATH",
|
||||
"OIDC_NAME_CLAIM_PATH",
|
||||
"OIDC_NAME",
|
||||
"OIDC_PICTURE_CLAIM_PATH",
|
||||
"PADDLE_PUBLIC_KEY",
|
||||
"PORT",
|
||||
"SECRET_PASSWORD",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue