Created
December 29, 2024 13:50
-
-
Save antonzy/c81e1facbac6c39817aee4645b0dec10 to your computer and use it in GitHub Desktop.
Stigg for Auth0 Login Action
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const ManagementClient = require('auth0').ManagementClient; | |
| const Stigg = require('@stigg/node-server-sdk').Stigg; | |
| const crypto = require('crypto'); | |
| /** | |
| * Handler that will be called during the execution of a PostLogin flow. | |
| * | |
| * @param {Event} event - Details about the user and the context in which they are logging in. | |
| * @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login. | |
| */ | |
| exports.onExecutePostLogin = async (event, api) => { | |
| const apiKey = event.secrets.STIGG_API_KEY; | |
| const individualFreePlanId = event.secrets.STIGG_INDIVIDUAL_PLAN_ID; | |
| const businessFreePlanId = event.secrets.STIGG_BUSINESS_PLAN_ID; | |
| const activeUsersFeatureId = event.secrets.STIGG_ACTIVE_USERS_FEATURE_ID; | |
| const applicationId = event.secrets.STIGG_APPLICATION_ID; | |
| const auth0Domain = event.secrets.DOMAIN; | |
| const auth0ClientId = event.secrets.CLIENT_ID; | |
| const auth0ClientSecret = event.secrets.CLIENT_SECRET; | |
| const isOrganization = !!event.organization; | |
| const customerAlreadyProvisioned = isOrganization ? event.organization?.metadata?.customer_provisioned : event.user.user_metadata?.customer_provisioned; | |
| const customerId = event.organization?.id || event.user.user_id; | |
| const customerName = event.organization?.display_name || event.user.name; | |
| const customerEmail = isOrganization ? undefined : event.user.email; | |
| const initialCustomerPlanId = isOrganization ? businessFreePlanId : individualFreePlanId; | |
| const organizationId = event.organization?.id; | |
| // store last login for organization in a separate key | |
| const lastLoginMetadataKey = `${organizationId}:last_login`; | |
| if (event.client.client_id !== applicationId) { | |
| return; | |
| } | |
| const managementClient = new ManagementClient({ | |
| domain: auth0Domain, | |
| clientId: auth0ClientId, | |
| clientSecret: auth0ClientSecret, | |
| }); | |
| const stigg = Stigg.initialize({ | |
| apiKey, | |
| realtimeUpdatesEnabled: false, | |
| enableRemoteConfig: false, | |
| }); | |
| const provisionCustomer = async () => { | |
| try { | |
| if (!customerAlreadyProvisioned && initialCustomerPlanId) { | |
| await stigg.provisionCustomer({ | |
| customerId, | |
| name: customerName, | |
| email: customerEmail, | |
| subscriptionParams: { | |
| planId: initialCustomerPlanId, | |
| }, | |
| }); | |
| } | |
| } catch (e) { | |
| if (!e.message.includes('Duplicate')) { | |
| console.error('Failed to provision customer', e); | |
| } | |
| return; | |
| } | |
| } | |
| const updateMetadata = async () => { | |
| try { | |
| if (isOrganization) { | |
| await managementClient.organizations.update( | |
| { | |
| id: customerId, | |
| }, | |
| { | |
| metadata: { | |
| customer_provisioned: 'true', | |
| }, | |
| }, | |
| ); | |
| } else { | |
| await managementClient.users.update( | |
| { | |
| id: customerId | |
| }, | |
| { | |
| user_metadata: { | |
| customer_provisioned: 'true', | |
| } | |
| } | |
| ) | |
| } | |
| } catch (e) { | |
| console.error(`Failed to update ${isOrganization ? 'organization' : 'user'}`, e); | |
| } | |
| } | |
| const checkActiveUsersEntitlement = async () => { | |
| const mauEntitlement = await stigg.getMeteredEntitlement({ | |
| customerId: organizationId, | |
| featureId: activeUsersFeatureId, | |
| options: { | |
| requestedUsage: 1, | |
| }, | |
| }); | |
| // block access to new MAU if the organization has exceeded the limit | |
| if (!mauEntitlement.hasAccess && mauEntitlement.accessDeniedReason === 'RequestedUsageExceedingLimit') { | |
| const lastLogin = new Date(event.user.app_metadata[lastLoginMetadataKey] || 0); | |
| const usagePeriodStart = new Date(mauEntitlement.usagePeriodStart?.getTime() || 0); | |
| const isNewMau = lastLogin < usagePeriodStart; | |
| if (isNewMau) { | |
| api.access.deny('Organization has exceeded the MAU limit, please contact your administrator.'); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| const reportActiveUsersEvent = async () => { | |
| const now = Date.now(); | |
| api.user.setAppMetadata(lastLoginMetadataKey, now); | |
| const userId = event.user.user_id; | |
| const eventName = 'user-login'; | |
| // generate a unique idempotency key for the event | |
| const idempotencyKey = crypto | |
| .createHash('md5') | |
| .update(`${userId}:${organizationId}:${eventName}:${now}`) | |
| .digest('hex'); | |
| try { | |
| await stigg.reportEvent({ | |
| customerId: organizationId, | |
| eventName, | |
| timestamp: now, | |
| idempotencyKey, | |
| dimensions: { | |
| user_id: userId, | |
| }, | |
| }); | |
| } catch (e) { | |
| console.error('Failed to report user login event', e); | |
| } | |
| } | |
| await provisionCustomer(); | |
| await updateMetadata(); | |
| if (!event.organization) { | |
| return; | |
| } | |
| const hasAccess = await checkActiveUsersEntitlement(); | |
| if (hasAccess) { | |
| await reportActiveUsersEvent(); | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment