diff --git a/src/client/oauth.ts b/src/client/oauth.ts index afbc6bc..384533b 100644 --- a/src/client/oauth.ts +++ b/src/client/oauth.ts @@ -18,6 +18,8 @@ export type OauthProvider = | "facebook" | "jumpcloud" | "twitch" + | "okta" + export const oauth = ( options: BaseOptions, @@ -35,9 +37,13 @@ export const oauth = ( isError: true, } - const base = process.env.NEXT_PUBLIC_SERVER_URL - const authUrl = `${base}/api/${options.name}/oauth/authorization/${provider}?clientOrigin=${encodeURIComponent(window.location.origin + `#${channelId}`)}` - + let authUrl = `/api/${options.name}/oauth/authorization/${provider}?clientOrigin=${encodeURIComponent(window.location.origin + `#${channelId}`)}` + + if (process.env.NEXT_PUBLIC_SERVER_URL) { + authUrl = `${process.env.NEXT_PUBLIC_SERVER_URL}${authUrl}` + } + + const width = 600 const height = 700 const left = window.screenX + (window.outerWidth - width) / 2 diff --git a/src/core/protocols/oauth/oidc_callback.ts b/src/core/protocols/oauth/oidc_callback.ts index b163ee8..4f1c9c1 100644 --- a/src/core/protocols/oauth/oidc_callback.ts +++ b/src/core/protocols/oauth/oidc_callback.ts @@ -43,7 +43,12 @@ export async function OIDCCallback( .discoveryRequest(issuer_url, { algorithm }) .then((response) => oauth.processDiscoveryResponse(issuer_url, response)) - const params = oauth.validateAuthResponse(as, client, current_url) + const params = oauth.validateAuthResponse( + as, + client, + current_url, + providerConfig?.params?.state || undefined, + ) const grantResponse = await oauth.authorizationCodeGrantRequest( as, diff --git a/src/providers/index.ts b/src/providers/index.ts index 10b9a8f..98f15bc 100644 --- a/src/providers/index.ts +++ b/src/providers/index.ts @@ -6,6 +6,7 @@ import DiscordAuthProvider from "./oauth2/discord.js" import FacebookAuthProvider from "./oauth2/facebook.js" import SlackAuthProvider from "./oidc/slack.js" import Auth0AuthProvider from "./oauth2/auth0.js" +import OktaAuthProvider from "./oidc/okta.js" import CognitoAuthProvider from "./oidc/cognito.js" import KeyCloakAuthProvider from "./oidc/keycloak.js" import PasskeyAuthProvider from "./passkey.js" @@ -34,4 +35,5 @@ export { JumpCloudAuthProvider, TwitchAuthProvider, PasswordProvider, + OktaAuthProvider, } diff --git a/src/providers/oidc/okta.ts b/src/providers/oidc/okta.ts new file mode 100644 index 0000000..edaec44 --- /dev/null +++ b/src/providers/oidc/okta.ts @@ -0,0 +1,106 @@ +import type { + AccountInfo, + OIDCProviderConfig, + OAuthBaseProviderConfig, +} from "../../types.js" + +interface OktaAuthConfig extends OAuthBaseProviderConfig { + domain: string +} + +/** + * Add Okta OIDC Provider + * + * #### Callback or Redirect URL pattern + * + * - For Admin + * ``` + * https://example.com/api/admin/oauth/callback/{name} + * ``` + * + * - For App + * ``` + * https://example.com/api/{app_name}/oauth/callback/{name} + * ``` + * + * #### Plugin Setup + * + * ```ts + * import { Plugin } from 'payload' + * import {adminAuthPlugin, appAuthPlugin} from "payload-auth-plugin" + * import {OktaAuthProvider} from "payload-auth-plugin/providers" + * + * export const plugins: Plugins[] = [ + * //For Admin + * adminAuthPlugin({ + * accountsCollectionSlug: 'adminAccounts', + * providers:[ + * OktaAuthProvider({g, + * domain: process.env.KEYCLOAK_DOMAIN as string, + * client_id: process.env.KEYCLOAK_CLIENT_ID as string, + * client_secret: process.env.KEYCLOAK_CLIENT_SECRET as string, + * }) + * ] + * }) + * + * // For App + * appAuthPlugin({ + * name: 'app' + * secret: process.env.APP_AUTH_SECRET, + * accountsCollectionSlug: 'adminAccounts', + * usersCollectionSlug: 'appUsers', + * accountsCollectionSlug: 'appAccounts', + * providers:[ + * OktaAuthProvider({ + * domain: process.env.KEYCLOAK_DOMAIN as string, + * client_id: process.env.KEYCLOAK_CLIENT_ID as string, + * client_secret: process.env.KEYCLOAK_CLIENT_SECRET as string, + * }) + * ] + * }) + * ] + * ``` + * + */ + +function encodeString(s: string): number { + let h = 0; + const l = s.length; + let i = 0; + if (l > 0) { + while (i < l) { + h = ((h << 5) - h + s.charCodeAt(i++)) | 0; // Bitwise operations to create a hash + } + } + return h; +} + + +function OktaAuthProvider(config: OktaAuthConfig): OIDCProviderConfig { + const { domain, ...restConfig } = config; + + const stateCode = encodeString(config.client_id).toString() + + return { + ...restConfig, + id: 'okta', + scope: "email openid profile", + issuer: `https://${domain}`, + name: "Okta", + algorithm: "oidc", + kind: "oauth", + params: { + state: `state-${stateCode}` + }, + profile: (profile): AccountInfo => { + return { + sub: profile.sub as string, + name: profile.name as string, + email: profile.email as string, + picture: profile.picture as string, + } + }, + } +} + +export default OktaAuthProvider