-
Notifications
You must be signed in to change notification settings - Fork 514
feat : add Okta Provider #1043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
feat : add Okta Provider #1043
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; | ||
| import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; | ||
| import { getJwtInfo } from "@stackframe/stack-shared/dist/utils/jwt"; | ||
| import { OAuthUserInfo, validateUserInfo } from "../utils"; | ||
| import { OAuthBaseProvider, TokenSet } from "./base"; | ||
|
|
||
|
|
||
| export class OktaProvider extends OAuthBaseProvider { | ||
| private oktaDomain : string; | ||
| private constructor( | ||
| oktaDomain: string, | ||
| ...args: ConstructorParameters<typeof OAuthBaseProvider> | ||
| ) { | ||
| super(...args); | ||
| this.oktaDomain = oktaDomain | ||
| } | ||
|
|
||
| static async create(options: { | ||
| clientId: string; | ||
| clientSecret: string; | ||
| oktaDomain: string; | ||
| }) { | ||
| const oktaDomain = options.oktaDomain; | ||
|
|
||
| if(!oktaDomain) throw new StackAssertionError("Okta domain is required ") | ||
|
|
||
| return new OktaProvider( | ||
| oktaDomain, | ||
| ...await OAuthBaseProvider.createConstructorArgs({ | ||
| issuer: `https://${oktaDomain}`, | ||
| authorizationEndpoint: `https://${oktaDomain}/v1/authorize`, | ||
| tokenEndpoint: `https://${oktaDomain}/v1/token`, | ||
| redirectUri: getEnvVariable("OAUTH_REDIRECT_URI")!, | ||
| jwksUri: `https://${oktaDomain}/v1/keys`, | ||
| baseScope: "openid email profile", | ||
| authorizationExtraParams: { response_mode: "form_post" }, | ||
| tokenEndpointAuthMethod: "client_secret_basic", | ||
| ...options, | ||
| })) | ||
| ; | ||
| } | ||
|
|
||
| async postProcessUserInfo(tokenSet: TokenSet): Promise<OAuthUserInfo> { | ||
| const rawUserInfoRes = await fetch(`https://${this.oktaDomain}/v1/userinfo`,{ | ||
| headers: { | ||
| Authorization: `Bearer ${tokenSet.accessToken}`, | ||
| }, | ||
| }); | ||
|
|
||
| if (!rawUserInfoRes.ok) { | ||
| throw new StackAssertionError( | ||
| "Error fetching user information from Okta", | ||
| { | ||
| status: rawUserInfoRes.status, | ||
| body: await rawUserInfoRes.text(), | ||
| jwtInfo: await getJwtInfo({ jwt: tokenSet.accessToken }), | ||
| } | ||
| ); | ||
| } | ||
|
|
||
| const rawUserInfo = await rawUserInfoRes.json(); | ||
|
|
||
| return validateUserInfo({ | ||
| accountId: rawUserInfo.sub, | ||
| displayName: rawUserInfo.name, | ||
| profileImageUrl: rawUserInfo.picture, | ||
| email: rawUserInfo.email, | ||
| emailVerified: rawUserInfo.email_verified, | ||
| }); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| async checkAccessTokenValidity(accessToken: string): Promise<boolean> { | ||
| const res = await fetch(`https://${this.oktaDomain}/v1/userinfo`,{ | ||
| headers: { | ||
| Authorization: `Bearer ${accessToken}`, | ||
| }, | ||
| }); | ||
| return res.ok; | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -44,6 +44,7 @@ function toTitle(id: string) { | |||||||||||||||||||||||||||||||||
| linkedin: "LinkedIn", | ||||||||||||||||||||||||||||||||||
| twitch: "Twitch", | ||||||||||||||||||||||||||||||||||
| x: "X", | ||||||||||||||||||||||||||||||||||
| okta: "Okta" | ||||||||||||||||||||||||||||||||||
| }[id]; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -63,6 +64,7 @@ export const providerFormSchema = yupObject({ | |||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||
| facebookConfigId: yupString().optional(), | ||||||||||||||||||||||||||||||||||
| microsoftTenantId: yupString().optional(), | ||||||||||||||||||||||||||||||||||
| oktaDomain: yupString().optional() | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export type ProviderFormValues = yup.InferType<typeof providerFormSchema> | ||||||||||||||||||||||||||||||||||
|
|
@@ -75,6 +77,7 @@ export function ProviderSettingDialog(props: Props & { open: boolean, onClose: ( | |||||||||||||||||||||||||||||||||
| clientSecret: (props.provider as any)?.clientSecret ?? "", | ||||||||||||||||||||||||||||||||||
| facebookConfigId: (props.provider as any)?.facebookConfigId ?? "", | ||||||||||||||||||||||||||||||||||
| microsoftTenantId: (props.provider as any)?.microsoftTenantId ?? "", | ||||||||||||||||||||||||||||||||||
| oktaDomain:(props.provider as any)?.oktaDomain?? "", | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const onSubmit = async (values: ProviderFormValues) => { | ||||||||||||||||||||||||||||||||||
|
|
@@ -88,6 +91,7 @@ export function ProviderSettingDialog(props: Props & { open: boolean, onClose: ( | |||||||||||||||||||||||||||||||||
| clientSecret: values.clientSecret || "", | ||||||||||||||||||||||||||||||||||
| facebookConfigId: values.facebookConfigId, | ||||||||||||||||||||||||||||||||||
| microsoftTenantId: values.microsoftTenantId, | ||||||||||||||||||||||||||||||||||
| oktaDomain: values.oktaDomain, | ||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
@@ -166,6 +170,15 @@ export function ProviderSettingDialog(props: Props & { open: boolean, onClose: ( | |||||||||||||||||||||||||||||||||
| placeholder="Tenant ID" | ||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| {props.id === 'Okta' && ( | ||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: case mismatch - should be
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx
Line: 174:174
Comment:
**logic:** case mismatch - should be `'okta'` (lowercase) to match the provider key in `toTitle` function and backend
```suggestion
{props.id === 'okta' && (
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The condition checks for View DetailsAnalysisOkta domain input field never renders due to incorrect provider ID caseWhat fails: The Okta domain input field is never displayed in the OAuth provider configuration dialog, preventing users from entering the required Okta domain when setting up Okta OAuth authentication. How to reproduce:
Root cause: The condition at line 174 in Expected behavior: The Okta domain input field should be displayed when configuring Okta OAuth, consistent with how the facebook and microsoft provider conditions work (lines 155 and 164, which use lowercase IDs). Fix: Changed line 174 from |
||||||||||||||||||||||||||||||||||
| <InputField | ||||||||||||||||||||||||||||||||||
| control={form.control} | ||||||||||||||||||||||||||||||||||
| name="oktaDomain" | ||||||||||||||||||||||||||||||||||
| label="Okta Domain (required if you are using Okta)" | ||||||||||||||||||||||||||||||||||
| placeholder="oktaDomain" | ||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+174
to
+181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Case mismatch prevents Okta domain field from rendering. The condition checks Apply this diff: - {props.id === 'Okta' && (
+ {props.id === 'okta' && (
<InputField
control={form.control}
name="oktaDomain"
label="Okta Domain (required if you are using Okta)"
placeholder="oktaDomain"
/>
)}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.