diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index 1cf169bb8..f835d4d72 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -91,11 +91,8 @@ export type RecipeInterface = { tenantId: string; securityOptions?: { enforceEmailBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; - checkBreachedPassword?: boolean; + enforceIpBan?: boolean; + ipAddress?: string; }; userContext: UserContext; }): Promise< @@ -114,7 +111,7 @@ export type RecipeInterface = { | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; } | { - status: "EMAIL_BANNED_ERROR" | "BREACHED_PASSWORD_ERROR" | "IP_BANNED_ERROR"; + status: "EMAIL_BANNED_ERROR" | "IP_BANNED_ERROR"; } | { // this can happen during account linking, if the primary user that this is going to be linked to is banned. @@ -132,11 +129,8 @@ export type RecipeInterface = { tenantId: string; securityOptions?: { enforceEmailBan?: boolean; - checkBreachedPassword?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; userContext: UserContext; }): Promise< @@ -147,7 +141,7 @@ export type RecipeInterface = { } | { status: "EMAIL_ALREADY_EXISTS_ERROR" } | { - status: "EMAIL_BANNED_ERROR" | "BREACHED_PASSWORD_ERROR" | "IP_BANNED_ERROR"; + status: "EMAIL_BANNED_ERROR" | "IP_BANNED_ERROR"; } >; @@ -159,11 +153,8 @@ export type RecipeInterface = { securityOptions?: { enforceUserBan?: boolean; enforceEmailBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; - checkBreachedPassword?: boolean; // will be false here by default even if users want to check breached password + enforceIpBan?: boolean; + ipAddress?: string; limitWrongCredentialsAttempt?: { enabled?: boolean; counterKey?: string; // by default, it is just email ID @@ -184,7 +175,7 @@ export type RecipeInterface = { | "SESSION_USER_ACCOUNT_INFO_ALREADY_ASSOCIATED_WITH_ANOTHER_PRIMARY_USER_ID_ERROR"; } | { - status: "EMAIL_BANNED_ERROR" | "BREACHED_PASSWORD_ERROR" | "IP_BANNED_ERROR"; + status: "EMAIL_BANNED_ERROR" | "IP_BANNED_ERROR"; } | { status: "USER_BANNED_ERROR"; @@ -205,11 +196,8 @@ export type RecipeInterface = { securityOptions?: { enforceUserBan?: boolean; enforceEmailBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; - checkBreachedPassword?: boolean; // will be false here by default even if users want to check breached password + enforceIpBan?: boolean; + ipAddress?: string; limitWrongCredentialsAttempt?: { enabled?: boolean; counterKey?: string; // by default, it is just email ID @@ -221,7 +209,7 @@ export type RecipeInterface = { | { status: "OK"; user: User; recipeUserId: RecipeUserId } | { status: "WRONG_CREDENTIALS_ERROR" } | { - status: "EMAIL_BANNED_ERROR" | "BREACHED_PASSWORD_ERROR" | "IP_BANNED_ERROR"; + status: "EMAIL_BANNED_ERROR" | "IP_BANNED_ERROR"; } | { status: "USER_BANNED_ERROR"; @@ -246,10 +234,8 @@ export type RecipeInterface = { securityOptions?: { enforceUserBan?: boolean; enforceEmailBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; userContext: UserContext; }): Promise< @@ -287,7 +273,6 @@ export type RecipeInterface = { userContext: UserContext; applyPasswordPolicy?: boolean; securityOptions?: { - checkBreachedPassword?: boolean; limitOldPasswordReuse?: { enabled?: boolean; numberOfOldPasswordsToCheck?: number; @@ -303,7 +288,7 @@ export type RecipeInterface = { reason: string; } | { status: "PASSWORD_POLICY_VIOLATED_ERROR"; failureReason: string } - | { status: "BREACHED_PASSWORD_ERROR" | "OLD_PASSWORD_REUSED_ERROR" } + | { status: "OLD_PASSWORD_REUSED_ERROR" } >; }; @@ -338,6 +323,7 @@ export type APIInterface = { | undefined | ((input: { googleRecaptchaToken?: string; + securityServiceRequestId?: string; formFields: { id: string; value: string; @@ -384,6 +370,7 @@ export type APIInterface = { | undefined | ((input: { googleRecaptchaToken?: string; + securityServiceRequestId?: string; formFields: { id: string; value: string; @@ -412,6 +399,7 @@ export type APIInterface = { | undefined | ((input: { googleRecaptchaToken?: string; + securityServiceRequestId?: string; formFields: { id: string; value: string; diff --git a/lib/ts/recipe/passwordless/types.ts b/lib/ts/recipe/passwordless/types.ts index 91d71710d..47f5d6bb2 100644 --- a/lib/ts/recipe/passwordless/types.ts +++ b/lib/ts/recipe/passwordless/types.ts @@ -125,10 +125,8 @@ export type RecipeInterface = { enforceUserBan?: boolean; // in case this is a sign in and not a sign up enforceEmailBan?: boolean; enforcePhoneNumberBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; } ) => Promise< @@ -160,10 +158,8 @@ export type RecipeInterface = { securityOptions?: { enforceEmailBan?: boolean; enforcePhoneNumberBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; }) => Promise< | { @@ -194,10 +190,8 @@ export type RecipeInterface = { enforceUserBan?: boolean; enforceEmailBan?: boolean; enforcePhoneNumberBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; } | { @@ -210,10 +204,8 @@ export type RecipeInterface = { enforceUserBan?: boolean; enforceEmailBan?: boolean; enforcePhoneNumberBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; } ) => Promise< @@ -393,6 +385,7 @@ export type APIInterface = { options: APIOptions; userContext: UserContext; googleRecaptchaToken?: string; + securityServiceRequestId?: string; } ) => Promise< | { @@ -408,7 +401,7 @@ export type APIInterface = { | GeneralErrorResponse >; - // we intentionally do not add googleRecaptcha in here cause + // we intentionally do not add googleRecaptcha or securityServiceRequestId in here cause // it's the same device that generates the code during createCode, and if // that's not a bot, nor is this. resendCodePOST?: ( diff --git a/lib/ts/recipe/session/types.ts b/lib/ts/recipe/session/types.ts index 13aece884..ad5772560 100644 --- a/lib/ts/recipe/session/types.ts +++ b/lib/ts/recipe/session/types.ts @@ -210,6 +210,12 @@ export interface VerifySessionOptions { antiCsrfCheck?: boolean; sessionRequired?: boolean; checkDatabase?: boolean; + securityChecks?: { + // should be checked only if checkDatabase is true. + enforceUserBan?: boolean; + enforceIpBan?: boolean; + ipAddress?: string; + }; overrideGlobalClaimValidators?: ( globalClaimValidators: SessionClaimValidator[], session: SessionContainerInterface, @@ -261,6 +267,8 @@ export type RecipeInterface = { userContext: UserContext; securityOptions?: { enforceUserBan?: boolean; + enforceIpBan?: boolean; + ipAddress?: string; }; }): Promise; @@ -296,25 +304,40 @@ export type RecipeInterface = { revokeMultipleSessions(input: { sessionHandles: string[]; userContext: UserContext }): Promise; - // Returns false if the sessionHandle does not exist + // Returns false if the sessionHandle does not exist or security options deny this session updateSessionDataInDatabase(input: { sessionHandle: string; newSessionData: any; + securityOptions?: { + enforceUserBan?: boolean; + enforceIpBan?: boolean; + ipAddress?: string; + }; userContext: UserContext; }): Promise; mergeIntoAccessTokenPayload(input: { sessionHandle: string; accessTokenPayloadUpdate: JSONObject; + securityOptions?: { + enforceUserBan?: boolean; + enforceIpBan?: boolean; + ipAddress?: string; + }; userContext: UserContext; }): Promise; /** - * @returns {Promise} Returns false if the sessionHandle does not exist + * Returns undefined if the sessionHandle does not exist or security options deny this session */ regenerateAccessToken(input: { accessToken: string; newAccessTokenPayload?: any; + securityOptions?: { + enforceUserBan?: boolean; + enforceIpBan?: boolean; + ipAddress?: string; + }; userContext: UserContext; }): Promise< | { @@ -379,7 +402,15 @@ export interface SessionContainerInterface { getSessionDataFromDatabase(userContext?: Record): Promise; - updateSessionDataInDatabase(newSessionData: any, userContext?: Record): Promise; + updateSessionDataInDatabase(input?: { + newSessionData: any; + securityOptions?: { + enforceUserBan?: boolean; + enforceIpBan?: boolean; + ipAddress?: string; + }; + userContext?: Record; + }): Promise; getUserId(userContext?: Record): string; @@ -400,7 +431,15 @@ export interface SessionContainerInterface { getAccessToken(userContext?: Record): string; - mergeIntoAccessTokenPayload(accessTokenPayloadUpdate: JSONObject, userContext?: Record): Promise; + mergeIntoAccessTokenPayload(input?: { + accessTokenPayloadUpdate: JSONObject; + securityOptions?: { + enforceUserBan?: boolean; + enforceIpBan?: boolean; + ipAddress?: string; + }; + userContext?: Record; + }): Promise; getTimeCreated(userContext?: Record): Promise; diff --git a/lib/ts/recipe/thirdparty/types.ts b/lib/ts/recipe/thirdparty/types.ts index 030f6e367..b1027eaed 100644 --- a/lib/ts/recipe/thirdparty/types.ts +++ b/lib/ts/recipe/thirdparty/types.ts @@ -178,10 +178,8 @@ export type RecipeInterface = { securityOptions?: { enforceUserBan?: boolean; enforceEmailBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; }): Promise< | { diff --git a/lib/ts/recipe/totp/types.ts b/lib/ts/recipe/totp/types.ts index 8b2c7a8c3..f77d702b0 100644 --- a/lib/ts/recipe/totp/types.ts +++ b/lib/ts/recipe/totp/types.ts @@ -64,10 +64,8 @@ export type RecipeInterface = { userContext: UserContext; securityOptions?: { enforceUserBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; }) => Promise< | { @@ -122,10 +120,8 @@ export type RecipeInterface = { userContext: UserContext; securityOptions?: { enforceUserBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; }) => Promise< | { @@ -155,10 +151,8 @@ export type RecipeInterface = { userContext: UserContext; securityOptions?: { enforceUserBan?: boolean; - ipBan?: { - enabled?: boolean; - ipAddress?: string; - }; + enforceIpBan?: boolean; + ipAddress?: string; }; }) => Promise< | { @@ -247,6 +241,7 @@ export type APIInterface = { | undefined | ((input: { googleRecaptchaToken?: string; + securityServiceRequestId?: string; deviceName: string; totp: string; options: APIOptions; @@ -276,6 +271,7 @@ export type APIInterface = { | undefined | ((input: { googleRecaptchaToken?: string; + securityServiceRequestId?: string; totp: string; options: APIOptions; session: SessionContainerInterface; diff --git a/lib/ts/types.ts b/lib/ts/types.ts index ccdf0aeb7..5e9bb4e76 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -73,6 +73,7 @@ export type TypeInput = { v2SecretKey?: string; v1SecretKey?: string; }; + securityServiceApiKey?: string; // this will be used for bruteforce, anomaly, and breached password detection services. override?: ( originalImplementation: SecurityFunctions, builder?: OverrideableBuilder @@ -80,85 +81,361 @@ export type TypeInput = { }; }; -export type InfoFromRequest = { +export type InfoFromRequestHeaders = { ipAddress?: string; userAgent?: string; }; -export type AnomalyServiceActionTypes = - | "sign-in" - | "sign-up" - | "session-refresh" - | "password-reset" - | "send-email" - | "send-sms" - | "mfa-verify" - | "mfa-setup"; +export type SecurityChecksActionTypes = + | "emailpassword-sign-in" + | "emailpassword-sign-up" + | "send-password-reset-email" + | "passwordless-send-email" + | "passwordless-send-sms" + | "totp-verify-device" + | "totp-verify-totp" + | "thirdparty-login" + | "emailverification-send-email"; export type RiskScores = { // all values are between 0 and 1, with 1 being highest risk - ipRisk: number; + requestIdInfo?: + | { + valid: true; + identification: { + data: { + visitorId: string; + requestId: string; + incognito: boolean; + linkedId: string; + tag: Record; + time: string; + timestamp: number; + url: string; + ip: string; + ipLocation: { + accuracyRadius: number; + latitude: number; + longitude: number; + postalCode: string; + timezone: string; + city: { + name: string; + }; + country: { + code: string; + name: string; + }; + continent: { + code: string; + name: string; + }; + subdivisions: Array<{ + isoCode: string; + name: string; + }>; + }; + browserDetails: { + browserName: string; + browserMajorVersion: string; + browserFullVersion: string; + os: string; + osVersion: string; + device: string; + userAgent: string; + }; + confidence: { + score: number; + }; + visitorFound: boolean; + firstSeenAt: { + global: string; + subscription: string; + }; + lastSeenAt: { + global: string | null; + subscription: string | null; + }; + }; + }; + botd: { + data: { + bot: { + result: string; + }; + url: string; + ip: string; + time: string; + userAgent: string; + requestId: string; + }; + }; + rootApps: { + data: { + result: boolean; + }; + }; + emulator: { + data: { + result: boolean; + }; + }; + ipInfo: { + data: { + v4: { + address: string; + geolocation: { + accuracyRadius: number; + latitude: number; + longitude: number; + postalCode: string; + timezone: string; + city: { + name: string; + }; + country: { + code: string; + name: string; + }; + continent: { + code: string; + name: string; + }; + subdivisions: Array<{ + isoCode: string; + name: string; + }>; + }; + asn: { + asn: string; + name: string; + network: string; + }; + datacenter: { + result: boolean; + name: string; + }; + }; + v6: { + address: string; + geolocation: { + accuracyRadius: number; + latitude: number; + longitude: number; + postalCode: string; + timezone: string; + city: { + name: string; + }; + country: { + code: string; + name: string; + }; + continent: { + code: string; + name: string; + }; + subdivisions: Array<{ + isoCode: string; + name: string; + }>; + }; + asn: { + asn: string; + name: string; + network: string; + }; + datacenter: { + result: boolean; + name: string; + }; + }; + }; + }; + ipBlocklist: { + data: { + result: boolean; + details: { + emailSpam: boolean; + attackSource: boolean; + }; + }; + }; + tor: { + data: { + result: boolean; + }; + }; + vpn: { + data: { + result: boolean; + originTimezone: string; + originCountry: string; + methods: { + timezoneMismatch: boolean; + publicVPN: boolean; + auxiliaryMobile: boolean; + osMismatch: boolean; + }; + }; + }; + proxy: { + data: { + result: boolean; + }; + }; + incognito: { + data: { + result: boolean; + }; + }; + tampering: { + data: { + result: boolean; + anomalyScore: number; + }; + }; + clonedApp: { + data: { + result: boolean; + }; + }; + factoryReset: { + data: { + time: string; + timestamp: number; + }; + }; + jailbroken: { + data: { + result: boolean; + }; + }; + frida: { + data: { + result: boolean; + }; + }; + privacySettings: { + data: { + result: boolean; + }; + }; + virtualMachine: { + data: { + result: boolean; + }; + }; + rawDeviceAttributes: { + data: { + architecture: { + value: number; + }; + audio: { + value: number; + }; + canvas: { + value: { + Winding: boolean; + Geometry: string; + Text: string; + }; + }; + colorDepth: { + value: number; + }; + colorGamut: { + value: string; + }; + contrast: { + value: number; + }; + cookiesEnabled: { + value: boolean; + }; + cpuClass: Record; + fonts: { + value: string[]; + }; + }; + }; + highActivity: { + data: { + result: boolean; + }; + }; + locationSpoofing: { + data: { + result: boolean; + }; + }; + remoteControl: { + data: { + result: boolean; + }; + }; + } + | { + valid: false; + }; phoneNumberRisk?: number; emailRisk?: number; - sessionRisk?: number; - userIdRisk?: number; + isBreachedPassword?: boolean; + bruteForce?: + | { + detected: false; + } + | { + detected: true; + key: string; + }; }; export type SecurityFunctions = { - getInfoFromRequest: (input: { request: BaseRequest; userContext: UserContext }) => InfoFromRequest; + getInfoFromRequest: (input: { request: BaseRequest; userContext: UserContext }) => InfoFromRequestHeaders; // this function will return hasProvidedV2SecretKey || hasProvidedV1SecretKey by default. - shouldPerformGoogleRecaptcha: (input: { - hasProvidedV2SecretKey: boolean; - hasProvidedV1SecretKey: boolean; - api: - | "password-reset-code-generation" - | "emailpassword-signin" - | "emailpassword-signup" - | "passwordless-create-code" - | "totp-verify-device" - | "totp-verify-totp"; + shouldEnforceGoogleRecaptchaTokenPresentInRequest: (input: { + tenantId: string; + actionType: SecurityChecksActionTypes; userContext: UserContext; }) => Promise; performGoogleRecaptchaV2: (input: { - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; clientResponseToken: string; userContext: UserContext; }) => Promise; performGoogleRecaptchaV1: (input: { - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; clientResponseToken: string; userContext: UserContext; }) => Promise; - // The apiKey for this will be fetched from the core during the /apiversion API call, and will be saved in memory for use. - // In case /apiversion has not yet been called, this function will call that API first. In case it has been called, but there - // is no API key for this, it means this function will not do anything and return undefined. This also means that if the user - // has added the license key in the core to enable this feature, they will have to restart the backend process once. - calculateRiskScoreUsingAnomalyService: (input: { - infoFromRequest: InfoFromRequest; - email?: string; - phoneNumber?: string; - sessionHandle?: string; + // this will return true if securityServiceApiKey is present in the config. + shouldEnforceSecurityServiceRequestIdPresentInRequest: (input: { tenantId: string; - userId?: string; - actionType: AnomalyServiceActionTypes; + actionType: SecurityChecksActionTypes; userContext: UserContext; - }) => Promise; // undefined means we have nothing to return, and we completely ignore this. + }) => Promise; - logToAnomalyService: (input: { - infoFromRequest: InfoFromRequest; + doSecurityChecks: (input: { + infoFromRequestHeaders?: InfoFromRequestHeaders; + passwordHash?: string; // to check against breached password + securityServiceRequestId?: string; email?: string; phoneNumber?: string; - sessionHandle?: string; - tenantId: string; - userId?: string; - action: AnomalyServiceActionTypes; - success: boolean; // this input is what differentiates this function from the one that generates the risk score. + bruteForce?: { + key: string; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; + }[]; + actionType?: SecurityChecksActionTypes; userContext: UserContext; - }) => void; // we intentionally do not return a promise cause this should be non blocking + }) => Promise; // undefined means we have nothing to return, and we completely ignore this. // these are all here and not in the respective recipes cause they are to be applied // only in the APIs and not in the recipe function. We still can't put them in the API @@ -169,113 +446,166 @@ export type SecurityFunctions = { tenantId: string; session?: SessionContainer; email: string; - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; }) => | { key: string; - millisecondsIntervalBetweenAttempts: number; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; }[] // is an array so that we can have multiple checks and fail the api if any one of them fail | undefined; // undefined means no rate limit getRateLimitForEmailPasswordSignUp: (input: { tenantId: string; session?: SessionContainer; email: string; - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; }) => | { key: string; - millisecondsIntervalBetweenAttempts: number; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; }[] | undefined; getRateLimitForThirdPartySignInUp: (input: { tenantId: string; session?: SessionContainer; thirdPartyId: string; // we intentionally do not give thirdPartyUserId because if we did, we'd have to query the thirdParty provider first - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; }) => | { key: string; - millisecondsIntervalBetweenAttempts: number; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; }[] | undefined; getRateLimitForSendingPasswordlessEmail: (input: { tenantId: string; session?: SessionContainer; email: string; - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; }) => | { key: string; - millisecondsIntervalBetweenAttempts: number; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; }[] | undefined; getRateLimitForSendingPasswordlessSms: (input: { tenantId: string; session?: SessionContainer; phoneNumber: string; - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; }) => | { key: string; - millisecondsIntervalBetweenAttempts: number; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; }[] | undefined; getRateLimitForResetPassword: (input: { tenantId: string; email: string; - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; }) => | { key: string; - millisecondsIntervalBetweenAttempts: number; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; }[] | undefined; getRateLimitForVerifyEmail: (input: { tenantId: string; session: SessionContainer; // we intentionally do not pass in the email here cause to fetch that, we'd need to query the core first - infoFromRequest: InfoFromRequest; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; }) => | { key: string; - millisecondsIntervalBetweenAttempts: number; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; }[] | undefined; - // these are functions to actually query the rate limit service. The api key for this - // will be fetched from the /apiversion API call, similar to the api key for the anomaly service. - setRateLimitForKey: (input: { - keys: { - key: string; - millisecondsIntervalBetweenAttempts: number; - }[]; + getRateLimitForTotpDeviceVerify: (input: { + tenantId: string; + session: SessionContainer; + deviceName: string; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; - }) => void; // should be non blocking, so we do not return a Promise - areAnyKeysRateLimited: (input: { - keys: { - key: string; - millisecondsIntervalBetweenAttempts: number; - }[]; + }) => + | { + key: string; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; + }[] + | undefined; + + getRateLimitForTotpVerify: (input: { + tenantId: string; + session: SessionContainer; + deviceName: string; + infoFromRequest: InfoFromRequestHeaders; userContext: UserContext; - }) => Promise; + }) => + | { + key: string; + maxRequests: { + limit: number; + perTimeIntervalMS: number; + }[]; + }[] + | undefined; - banUser: (input: { - userId: string; // can be a primary or recipe user id, either way, the primary user id is banned + ban: (input: { + userId?: string; // can be a primary or recipe user id, either way, the primary user id is banned + ipAddress?: string; + email?: string; + phoneNumber?: string; + userContext: UserContext; }) => Promise; - isUserBanned: (input: { - userId: string; // can be a primary or recipe user id, either way, the primary user id is banned - }) => Promise; + getIsBanned: (input: { + userId?: string; // can be a primary or recipe user id, either way, the primary user id is banned + ipAddress?: string; + email?: string; + phoneNumber?: string; + userContext: UserContext; + }) => Promise<{ + userIdBanned?: boolean; + ipAddressBanned?: boolean; + emailBanned?: boolean; + phoneNumberBanned?: boolean; + }>; - unbanUser: (input: { - userId: string; // can be a primary or recipe user id, either way, the primary user id is unbanned + unban: (input: { + userId?: string; // can be a primary or recipe user id, either way, the primary user id is banned + ipAddress?: string; + email?: string; + phoneNumber?: string; + userContext: UserContext; }) => Promise; };