Skip to content

feat: add Okta sync for user and group attribute statements#12

Open
abdelrahman-zaki wants to merge 6 commits into
kinde-starter-kits:mainfrom
abdelrahman-zaki:feat/sync-attributes-okta
Open

feat: add Okta sync for user and group attribute statements#12
abdelrahman-zaki wants to merge 6 commits into
kinde-starter-kits:mainfrom
abdelrahman-zaki:feat/sync-attributes-okta

Conversation

@abdelrahman-zaki
Copy link
Copy Markdown
Contributor

@abdelrahman-zaki abdelrahman-zaki commented Aug 19, 2025

Explain your changes

This workflow syncs a user's mobilePhone, userType, and groups from Okta SAML attribute statements into the phone_number, user_type, and groups properties in Kinde after authentication.

Checklist

🛟 If you need help, consider asking for advice over in the Kinde community.

Summary by CodeRabbit

  • New Features
    • Automatically syncs Okta SAML attributes into user profiles after sign-in, including multi-value fields (e.g., groups), keeping profiles up to date without manual edits.
    • Runs immediately post-authentication and applies only to users signing in via the Okta connection; includes graceful handling of sync issues to avoid sign-in disruption.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 19, 2025

Walkthrough

Adds a new PostAuthentication workflow that validates an Okta connection, parses Okta SAML attribute statements, maps attributes to Kinde user properties (joining multi-value attributes), and PATCHes the user’s properties via the Kinde API. Exposes workflowSettings and a default handlePostAuth handler.

Changes

Cohort / File(s) Summary
Post-auth Okta SAML attribute sync
postUserAuthentication/syncAttributesOktaWorkflow.ts
New workflow file. Validates OKTA_CONNECTION_ID, parses Okta SAML attributeStatements into a lowercased name→values map, maps attributes to Kinde properties (supports multi-value join), constructs propertiesToUpdate, and PATCHes users/{userId}/properties. Exports workflowSettings and default handlePostAuth(event).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant K as Kinde Auth
  participant W as OktaAttributesSync
  participant O as Okta (SAML)
  participant A as Kinde API

  U->>K: Authenticate via Okta connection
  K-->>U: Authentication success
  K->>W: PostAuthentication event (userId, context, assertion)
  W->>W: Validate context.auth.connectionId == OKTA_CONNECTION_ID
  W->>O: Read assertion.attributeStatements
  O-->>W: Attributes (name -> values)
  W->>W: Build attributes map & propertiesToUpdate (join multi-values)
  alt propertiesToUpdate not empty
    W->>A: PATCH users/{userId}/properties { propertiesToUpdate }
    A-->>W: 200 OK / error
    W-->>K: Log result (errors retained)
  else no updates
    W-->>K: Exit (no-op)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • DanielRivers
  • DaveOrDead

Poem

A rabbit found SAML in the night,
Lowercased names and joined groups tight.
Hopped to Kinde with a tiny cheer,
Patched the props so users appear.
🥕🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between daaf9af and 8cd1034.

📒 Files selected for processing (1)
  • postUserAuthentication/syncAttributesOktaWorkflow.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • postUserAuthentication/syncAttributesOktaWorkflow.ts
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@abdelrahman-zaki abdelrahman-zaki changed the title Feat/sync attributes okta feat: add Okta sync for user and group attribute statements Aug 19, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
postUserAuthentication/syncUserPhoneGoogleWorkspace.ts (1)

37-48: Confirm workflowSettings.id uniqueness expectations.

Same note as the Okta file about id: "postAuthentication". If this should be unique per workflow instance, rename; if it denotes the stage, you’re fine.

See the repository-wide scan script in my earlier comment to review ids.

🧹 Nitpick comments (8)
postUserAuthentication/syncAttributesOkta.ts (4)

95-98: Consider accepting common Okta attribute name variants to reduce setup friction.

Many Okta admins instinctively use mobilePhone and userType as attribute Names. Supporting these as fallbacks (while keeping the documented mapping) makes the example more forgiving.

Apply this diff:

-    const phoneAttrNames = ["phone_number"];
-    const userTypeAttrNames = ["user_type"];
-    const groupsAttrNames = ["groups"];
+    // Accept documented names plus common variants admins may configure
+    const phoneAttrNames = ["phone_number", "phone", "mobilephone"];
+    const userTypeAttrNames = ["user_type", "usertype"];
+    const groupsAttrNames = ["groups", "group"];

110-112: Stabilize and deduplicate groups to avoid noisy updates.

Okta may return groups in varying order. Sorting and deduplicating prevents churn on every login and keeps the property stable.

Apply this diff:

-    const groupsArray = getAllStrings(findAttr(groupsAttrNames));
+    const groupsArray = Array.from(
+        new Set(getAllStrings(findAttr(groupsAttrNames)))
+    ).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));

122-129: Best-effort property updates to reduce blast radius of partial failures.

A single failed PUT currently rejects the entire Promise.all and stops the workflow. If partial updates are acceptable, switch to Promise.allSettled so one failing property doesn’t block others.

Apply this diff:

-    await Promise.all([
+    await Promise.allSettled([
         setProp(phonePropertyKey, phoneValue),
         setProp(userTypePropertyKey, userTypeValue),
         setProp(groupsPropertyKey, groupsValue),
     ]);

Also applies to: 131-135


113-115: Optional: Decide on behavior when attributes are absent (clear vs. retain).

Currently, properties are only set when a value exists; missing attributes leave existing values untouched. If you need “true sync” semantics (e.g., clear groups when none are sent), we can add a configurable mode (via env var) to explicitly clear or unset properties when absent.

Would you like me to add a CLEAR_ON_ABSENT flag and implement property clearing safely?

postUserAuthentication/syncUserPhoneGoogleWorkspace.ts (4)

56-58: Harden the connection gating to also require the env var be set.

Mirror the Okta check so the workflow does nothing if GOOGLE_WORKSPACE_CONNECTION_ID is unset.

Apply this diff:

-    if (connectionId !== googleWorkspaceConnectionId) return;
+    if (!googleWorkspaceConnectionId || connectionId !== googleWorkspaceConnectionId) return;

71-71: Make phone extraction resilient to non-string values.

Align with the Okta workflow by coercing to string before trimming.

Apply this diff:

-    const phoneValue = phoneAttr?.values?.[0]?.value?.trim() || null;
+    const phoneValue = (phoneAttr?.values?.[0]?.value ?? "").toString().trim() || null;

79-81: URL-encode the property key in the endpoint path.

Safer and consistent with the Okta workflow.

Apply this diff:

-    await kindeAPI.put({
-        endpoint: `users/${userId}/properties/${phonePropertyKey}?value=${encodeURIComponent(phoneValue)}`
-    });
+    await kindeAPI.put({
+        endpoint: `users/${userId}/properties/${encodeURIComponent(
+            phonePropertyKey
+        )}?value=${encodeURIComponent(phoneValue)}`,
+    });

50-53: Reduce duplication of SAML helper types.

These types are identical to those in the Okta workflow. Consider moving them to a small shared module (e.g., postUserAuthentication/_samlTypes.ts) and importing in both files.

I can factor out a minimal shared module and update imports in both files if you’d like.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6b6cc and 5c890c1.

📒 Files selected for processing (2)
  • postUserAuthentication/syncAttributesOkta.ts (1 hunks)
  • postUserAuthentication/syncUserPhoneGoogleWorkspace.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-19T06:39:44.342Z
Learnt from: ages96
PR: kinde-starter-kits/workflow-examples#6
File: postUserAuthentication/impossibleTravelWorkflow.ts:0-0
Timestamp: 2025-06-19T06:39:44.342Z
Learning: In Kinde's postUserAuthentication workflows, after the OTP authentication step, event.context.user consistently returns null for fields like name and email, with only the user ID being available. Therefore, kindeAPI.get() is required to retrieve the full user details including name and email fields.

Applied to files:

  • postUserAuthentication/syncUserPhoneGoogleWorkspace.ts
🧬 Code Graph Analysis (2)
postUserAuthentication/syncUserPhoneGoogleWorkspace.ts (1)
postUserAuthentication/syncNewUserToHubspotWorkflow.ts (2)
  • handlePostAuth (45-123)
  • extractMatchingProperties (69-79)
postUserAuthentication/syncAttributesOkta.ts (1)
postUserAuthentication/syncNewUserToHubspotWorkflow.ts (2)
  • handlePostAuth (45-123)
  • extractMatchingProperties (69-79)
🔇 Additional comments (3)
postUserAuthentication/syncAttributesOkta.ts (2)

79-87: Connection gating and provider data access look correct.

You correctly gate execution by OKTA_CONNECTION_ID and early-return when SAML assertion data is missing. This prevents unnecessary API calls and avoids null dereferences.


61-72: Confirm uniqueness of workflowSettings.id across workflows
We’ve identified multiple workflows sharing the same id: "postAuthentication" which could collide at registration:

  • postUserAuthentication/syncUserPhoneGoogleWorkspace.ts
  • postUserAuthentication/syncAttributesOkta.ts
  • postUserAuthentication/syncNewUserToHubspotWorkflow.ts

Please verify—according to the runtime’s registration logic—whether workflowSettings.id must be globally unique. If so, update these IDs (e.g., "postAuthenticationOktaSync", "postAuthenticationGooglePhoneSync", "postAuthenticationHubspotSync") to avoid conflicts.

postUserAuthentication/syncUserPhoneGoogleWorkspace.ts (1)

54-76: Good: Only uses userId from event context.

Per prior learnings, only the user ID is reliable here post-OTP. Using event.context.user.id exclusively and fetching nothing else is correct.

@abdelrahman-zaki abdelrahman-zaki force-pushed the feat/sync-attributes-okta branch from 5c890c1 to 5b62cb6 Compare August 19, 2025 22:59
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
postUserAuthentication/syncAttributesOktaWorkflow.ts (6)

65-76: Confirm failure behavior: do you really want to block login if the sync fails?

With failurePolicy.action: "stop", an exception (e.g., Kinde API outage) will stop the workflow, which can block user sign-in depending on platform behavior. If non-critical, consider a fail-open approach.

Apply if you prefer to not block auth on sync failures:

 export const workflowSettings: WorkflowSettings = {
   id: "postAuthentication",
   name: "OktaAttributesSync",
   failurePolicy: {
-        action: "stop",
+        action: "continue",
   },
   trigger: WorkflowTrigger.PostAuthentication,
   bindings: {
     "kinde.env": {},
     url: {},
   },
 };

82-86: Consider adding common Okta attribute aliases as fallbacks.

Many Okta setups default to mobilePhone and userType. Supporting these (in addition to the normalized names) reduces setup friction.

Minimal change to accept both (case-insensitive) without altering the rest of the logic:

 const attributeSyncConfig = [
-    { samlName: "phone_number", kindeKey: "phone_number", multiValue: false },
-    { samlName: "user_type", kindeKey: "user_type", multiValue: false },
+    { samlName: "phone_number", kindeKey: "phone_number", multiValue: false },
+    { samlName: "mobilephone", kindeKey: "phone_number", multiValue: false },
+    { samlName: "user_type", kindeKey: "user_type", multiValue: false },
+    { samlName: "usertype", kindeKey: "user_type", multiValue: false },
     { samlName: "groups", kindeKey: "groups", multiValue: true },
 ];

88-97: Add guardrails/logging for missing connection or attributes to aid troubleshooting.

Silent returns make operational debugging harder. A couple of low-noise logs help confirm why the sync skipped.

 export default async function handlePostAuth(event: onPostAuthenticationEvent) {
   const connectionId = event.context.auth.connectionId;
   const oktaConnectionId = getEnvironmentVariable("OKTA_CONNECTION_ID")?.value;
-  if (!oktaConnectionId || connectionId !== oktaConnectionId) return;
+  if (!oktaConnectionId) {
+    console.warn("OKTA_CONNECTION_ID not set. Skipping Okta attribute sync.");
+    return;
+  }
+  if (connectionId !== oktaConnectionId) return;
 
   const attributeStatements =
       event.context.auth.provider?.data?.assertion
           ?.attributeStatements as SamlAttributeStatement[] | undefined;
-  if (!attributeStatements?.length) return;
+  if (!attributeStatements?.length) {
+    console.log("No SAML attribute statements on assertion. Nothing to sync.");
+    return;
+  }

98-112: Accumulate duplicate SAML attributes across statements and de-duplicate values.

Okta can emit the same attribute in multiple statements. Current code overwrites earlier values. Merge instead to avoid data loss.

-const samlAttributesMap = (attributeStatements ?? [])
-    .flatMap((statement) => statement.attributes ?? [])
-    .reduce((acc, attr) => {
-        const name = attr.name?.toLowerCase().trim();
-        if (name) {
-            const values = (attr.values ?? [])
-                .map((v) => v.value?.trim())
-                .filter((v): v is string => !!v);
-            if (values.length > 0) {
-                acc.set(name, values);
-            }
-        }
-        return acc;
-    }, new Map<string, string[]>());
+const samlAttributesMap = (attributeStatements ?? [])
+  .flatMap((statement) => statement.attributes ?? [])
+  .reduce((acc, attr) => {
+    const name = attr.name?.toLowerCase().trim();
+    if (name) {
+      const values = (attr.values ?? [])
+        .map((v) => v.value?.trim())
+        .filter((v): v is string => !!v);
+      if (values.length > 0) {
+        const existing = acc.get(name) ?? [];
+        const merged = Array.from(new Set([...existing, ...values]));
+        acc.set(name, merged);
+      }
+    }
+    return acc;
+  }, new Map<string, string[]>());

115-124: Normalize multi-value properties and avoid duplicate group entries.

Join is fine, but duplicates can creep in. Consider de-duping to keep properties tidy.

     if (values && values.length > 0) {
       if (config.multiValue) {
-        propertiesToUpdate[config.kindeKey] = values.join(",");
+        propertiesToUpdate[config.kindeKey] = Array.from(new Set(values)).join(",");
       } else {
         propertiesToUpdate[config.kindeKey] = values[0];
       }
     }

53-64: Reconsider exposing phone numbers in tokens unless strictly required.

The comment advises toggling “Private” off so values appear in tokens. Phone numbers are PII; only expose in tokens if a downstream consumer needs them, otherwise leave as private to reduce token size and leakage risk.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0cf8b22 and d540884.

📒 Files selected for processing (1)
  • postUserAuthentication/syncAttributesOktaWorkflow.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ages96
PR: kinde-starter-kits/workflow-examples#6
File: postUserAuthentication/impossibleTravelWorkflow.ts:0-0
Timestamp: 2025-06-19T06:39:44.342Z
Learning: In Kinde's postUserAuthentication workflows, after the OTP authentication step, event.context.user consistently returns null for fields like name and email, with only the user ID being available. Therefore, kindeAPI.get() is required to retrieve the full user details including name and email fields.
🧬 Code Graph Analysis (1)
postUserAuthentication/syncAttributesOktaWorkflow.ts (2)
postUserAuthentication/syncNewUserToHubspotWorkflow.ts (2)
  • handlePostAuth (45-123)
  • extractMatchingProperties (69-79)
postUserAuthentication/checkIPWithAbuseIPDBWorkflow.ts (1)
  • handlePostAuth (237-394)
🔇 Additional comments (2)
postUserAuthentication/syncAttributesOktaWorkflow.ts (2)

128-130: LGTM: Using only event.context.user.id is correct for post-auth flows.

Per prior learnings, only the user ID is reliably present at this stage; name/email often aren’t. No extra user fetch needed here.


131-134: Verify Kinde API client’s PATCH payload key

It’s unclear from the codebase whether kindeAPI.patch expects its JSON payload under a body (or data) property or if it treats params as the request body. Most HTTP clients (e.g. Axios, Fetch wrappers) map params to query‐string parameters and use body (or data) for the request payload. If patch is implemented this way, your current call may be sending { properties: … } in the URL rather than the HTTP body, causing updates to silently fail.

Please confirm the createKindeAPI contract in @kinde/infrastructure—specifically the signature of patch—and adjust accordingly. For example, if body is required, update:

 await kindeAPI.patch({
   endpoint: `users/${userId}/properties`,
-  params: { properties: propertiesToUpdate },
+  body:   { properties: propertiesToUpdate },
 });

Key locations to review:

  • postUserAuthentication/syncAttributesOktaWorkflow.ts (lines 131–134)
  • existingPassword/dripFeedMigrationWorkflow.ts (lines 114–117)

Copy link
Copy Markdown
Contributor

@DaveOrDead DaveOrDead left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all looks great, can you add an entry to the Readme, and wrap the API call in a try/catch

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (3)
postUserAuthentication/syncAttributesOktaWorkflow.ts (3)

21-24: Doc nit: grammar fix in comments.
Change “setup” → “set up” for clarity.

-// * KINDE_WF_M2M_CLIENT_SECRET - Ensure this is setup with sensitive flag
-//   enabled to prevent accidental sharing
+// * KINDE_WF_M2M_CLIENT_SECRET - Ensure this is set up with the sensitive flag
+//   enabled to prevent accidental sharing

65-76: Unused binding “url”.
If not required by createKindeAPI in this runtime, drop it to reduce surface area. If required, ignore this.

 export const workflowSettings: WorkflowSettings = {
@@
     trigger: WorkflowTrigger.PostAuthentication,
     bindings: {
         "kinde.env": {},
-        url: {},
     },
 };

128-130: Guard against missing user ID.
Per prior learning, only ID is guaranteed; add a defensive check.

-    const kindeAPI = await createKindeAPI(event);
-    const userId = event.context.user.id;
+    const kindeAPI = await createKindeAPI(event);
+    const userId = event.context.user?.id;
+    if (!userId) return;

Ensure this guard won’t mask real issues if user.id should always be present in your tenant’s flow.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d540884 and daaf9af.

📒 Files selected for processing (1)
  • postUserAuthentication/syncAttributesOktaWorkflow.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: ages96
PR: kinde-starter-kits/workflow-examples#6
File: postUserAuthentication/impossibleTravelWorkflow.ts:0-0
Timestamp: 2025-06-19T06:39:44.342Z
Learning: In Kinde's postUserAuthentication workflows, after the OTP authentication step, event.context.user consistently returns null for fields like name and email, with only the user ID being available. Therefore, kindeAPI.get() is required to retrieve the full user details including name and email fields.
🧬 Code graph analysis (1)
postUserAuthentication/syncAttributesOktaWorkflow.ts (1)
postUserAuthentication/syncAttributesSamlWorkflow.ts (4)
  • handlePostAuth (73-127)
  • acc (84-95)
  • statement (83-83)
  • v (88-88)
🔇 Additional comments (4)
postUserAuthentication/syncAttributesOktaWorkflow.ts (4)

1-7: Imports look correct and minimal.


93-97: SAML attribute extraction is defensively coded. LGTM.


98-112: Attribute normalization and filtering look solid.
Lowercasing names and trimming values prevents common mismatches.


126-126: Early exit when nothing to update is good.

Comment on lines +78 to +86
type SamlValue = { value?: string };
type SamlAttribute = { name?: string; values?: SamlValue[] };
type SamlAttributeStatement = { attributes?: SamlAttribute[] };

const attributeSyncConfig = [
{ samlName: "phone_number", kindeKey: "phone_number", multiValue: false },
{ samlName: "user_type", kindeKey: "user_type", multiValue: false },
{ samlName: "groups", kindeKey: "groups", multiValue: true },
];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Type the mapping and allow multiple SAML names per property.
Prevents typos, enables future aliasing (e.g., “mobilePhone” vs “phone_number”), and centralizes normalization.

 type SamlValue = { value?: string };
 type SamlAttribute = { name?: string; values?: SamlValue[] };
 type SamlAttributeStatement = { attributes?: SamlAttribute[] };
 
-const attributeSyncConfig = [
-    { samlName: "phone_number", kindeKey: "phone_number", multiValue: false },
-    { samlName: "user_type", kindeKey: "user_type", multiValue: false },
-    { samlName: "groups", kindeKey: "groups", multiValue: true },
-];
+type AttributeSyncMapping = {
+    samlNames: string[];          // all names will be normalized to lowercase
+    kindeKey: string;
+    multiValue: boolean;
+};
+
+const attributeSyncConfig: readonly AttributeSyncMapping[] = [
+    { samlNames: ["phone_number"], kindeKey: "phone_number", multiValue: false },
+    { samlNames: ["user_type"],    kindeKey: "user_type",    multiValue: false },
+    { samlNames: ["groups"],       kindeKey: "groups",       multiValue: true  },
+] as const;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type SamlValue = { value?: string };
type SamlAttribute = { name?: string; values?: SamlValue[] };
type SamlAttributeStatement = { attributes?: SamlAttribute[] };
const attributeSyncConfig = [
{ samlName: "phone_number", kindeKey: "phone_number", multiValue: false },
{ samlName: "user_type", kindeKey: "user_type", multiValue: false },
{ samlName: "groups", kindeKey: "groups", multiValue: true },
];
type SamlValue = { value?: string };
type SamlAttribute = { name?: string; values?: SamlValue[] };
type SamlAttributeStatement = { attributes?: SamlAttribute[] };
type AttributeSyncMapping = {
samlNames: string[]; // all names will be normalized to lowercase
kindeKey: string;
multiValue: boolean;
};
const attributeSyncConfig: readonly AttributeSyncMapping[] = [
{ samlNames: ["phone_number"], kindeKey: "phone_number", multiValue: false },
{ samlNames: ["user_type"], kindeKey: "user_type", multiValue: false },
{ samlNames: ["groups"], kindeKey: "groups", multiValue: true },
] as const;
🤖 Prompt for AI Agents
In postUserAuthentication/syncAttributesOktaWorkflow.ts around lines 78 to 86,
the attributeSyncConfig is currently untyped and only supports a single samlName
per entry; change it to a typed mapping that allows samlNames: string[] (e.g.,
interface AttributeMapping { samlNames: string[]; kindeKey: string; multiValue:
boolean }) and update the config entries to use samlNames arrays (allowing
aliases), then update the code that reads attributes to iterate/scan all
samlNames when resolving a Kinde key (pick first present SAML attribute or merge
values for multiValue), and adjust types/usages accordingly so TypeScript
enforces the new shape and prevents typos.

Comment on lines +88 to +92
export default async function handlePostAuth(event: onPostAuthenticationEvent) {
const connectionId = event.context.auth.connectionId;
const oktaConnectionId = getEnvironmentVariable("OKTA_CONNECTION_ID")?.value;
if (!oktaConnectionId || connectionId !== oktaConnectionId) return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard on SAML protocol before doing any work.
Avoids unnecessary processing on non-SAML providers.

 export default async function handlePostAuth(event: onPostAuthenticationEvent) {
+    if (event.context?.auth?.provider?.protocol !== "saml") return;
     const connectionId = event.context.auth.connectionId;
     const oktaConnectionId = getEnvironmentVariable("OKTA_CONNECTION_ID")?.value;
     if (!oktaConnectionId || connectionId !== oktaConnectionId) return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default async function handlePostAuth(event: onPostAuthenticationEvent) {
const connectionId = event.context.auth.connectionId;
const oktaConnectionId = getEnvironmentVariable("OKTA_CONNECTION_ID")?.value;
if (!oktaConnectionId || connectionId !== oktaConnectionId) return;
export default async function handlePostAuth(event: onPostAuthenticationEvent) {
if (event.context?.auth?.provider?.protocol !== "saml") return;
const connectionId = event.context.auth.connectionId;
const oktaConnectionId = getEnvironmentVariable("OKTA_CONNECTION_ID")?.value;
if (!oktaConnectionId || connectionId !== oktaConnectionId) return;
🤖 Prompt for AI Agents
In postUserAuthentication/syncAttributesOktaWorkflow.ts around lines 88-92, add
an early return that guards on the authentication protocol being SAML before
proceeding; specifically, inspect the protocol on the incoming event (prefer
event.context.connection?.protocol and fall back to event.context.protocol) and
if the value is not 'saml2' or 'saml' return immediately to avoid running
Okta-specific logic for non-SAML providers.

Comment thread postUserAuthentication/syncAttributesOktaWorkflow.ts
Comment thread postUserAuthentication/syncAttributesOktaWorkflow.ts Outdated
@abdelrahman-zaki
Copy link
Copy Markdown
Contributor Author

Wrapped the API call in a try catch, updated the id, and switched the failurePolicy from stop to continue. I also added the README entry in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants