MCPcopy Index your code
hub / github.com/simstudioai/sim / incrementOTPAttempts

Function incrementOTPAttempts

apps/sim/lib/core/security/otp.ts:177–230  ·  view source on GitHub ↗
(
  kind: DeploymentKind,
  deploymentId: string,
  email: string,
  currentValue: string
)

Source from the content-addressed store, hash-verified

175 * `'incremented'` otherwise. The DB path uses optimistic locking with retry.
176 */
177export async function incrementOTPAttempts(
178 kind: DeploymentKind,
179 deploymentId: string,
180 email: string,
181 currentValue: string
182): Promise<'locked' | 'incremented'> {
183 const keys = OTP_KEYS[kind]
184 const storageMethod = getStorageMethod()
185
186 if (storageMethod === 'redis') {
187 const redis = getRedisClient()
188 if (!redis) throw new Error('Redis configured but client unavailable')
189 const key = keys.redisKey(email, deploymentId)
190 const result = await redis.eval(ATOMIC_INCREMENT_SCRIPT, 1, key, MAX_OTP_ATTEMPTS)
191 if (result === null || result === 'LOCKED') return 'locked'
192 return 'incremented'
193 }
194
195 const identifier = keys.dbIdentifier(email, deploymentId)
196 const MAX_RETRIES = 3
197 let value = currentValue
198
199 for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
200 const { otp, attempts } = decodeOTPValue(value)
201 const newAttempts = attempts + 1
202
203 if (newAttempts >= MAX_OTP_ATTEMPTS) {
204 await db.delete(verification).where(eq(verification.identifier, identifier))
205 return 'locked'
206 }
207
208 const newValue = encodeOTPValue(otp, newAttempts)
209 const updated = await db
210 .update(verification)
211 .set({ value: newValue, updatedAt: new Date() })
212 .where(and(eq(verification.identifier, identifier), eq(verification.value, value)))
213 .returning({ id: verification.id })
214
215 if (updated.length > 0) return 'incremented'
216
217 const fresh = await getOTP(kind, deploymentId, email)
218 if (!fresh) return 'locked'
219 value = fresh
220 }
221
222 /**
223 * Retry exhaustion under heavy DB-path contention: this request did not
224 * succeed in writing its own +1, so the stored count may not reflect it.
225 * Fail closed — invalidate the OTP rather than return `'incremented'` with
226 * a possibly-undercounted attempt total.
227 */
228 await db.delete(verification).where(eq(verification.identifier, identifier))
229 return 'locked'
230}
231
232export async function deleteOTP(
233 kind: DeploymentKind,

Callers 2

route.tsFile · 0.90
route.tsFile · 0.90

Calls 8

getStorageMethodFunction · 0.90
getRedisClientFunction · 0.90
decodeOTPValueFunction · 0.85
encodeOTPValueFunction · 0.85
getOTPFunction · 0.85
deleteMethod · 0.65
setMethod · 0.65
eqFunction · 0.50

Tested by

no test coverage detected