Access Codes

Phone-number verification for signup and phone changes: POST /request-access-code sends a 6-digit code by SMS and POST /validate-access-code exchanges the code for a validation_token that CreateUser requires. Both are called by the Cloudflare Workers in the edge repo. Records live in the access-codes table, keyed by phone number; error bodies are the AccessCodeError schema in the API Specification.

Request

  1. Resolve the phone number: user_id in the body means an authenticated phone change and uses the number on file (400 PHONE_NUMBER_NOT_FOUND); otherwise phone_number is required (400 MISSING_IDENTIFIER), reduced to digits, must be 10 digits (400 INVALID_PHONE_NUMBER) and must not belong to an existing user (409 PHONE_NUMBER_EXISTS).

  2. Load the existing record for the number and apply the backoff; a caller inside the wait window gets 425 with next_request_available seconds.

  3. Generate a 6-digit access_code and a 6-digit oob_code, and write the record conditionally: no record may exist, or created_on must still match the record just read. A conflict from a concurrent request is answered as 425.

  4. Queue the SMS on {env}-user-service-sms ({request_id, phone_number, message: "Your FloatMe Access Code is <code>."}) for delivery. If the send fails the record is rolled back to the previous one (or deleted) so the failed attempt does not throttle the retry, and the response is 500.

  5. 201 with oob_code and next_request_available.

Backoff

request_count increments for every code issued inside the 15-minute expiry window and resets once the newest record is older than 15 minutes.

Requests already made in the window Wait before the next code

0

none

1

60s

2

120s

3 or more

900s

Source: pkg/api/access_code.go

SMS Delivery

The {env}-user-service-sms-worker Lambda consumes {env}-user-service-sms one message at a time and sends message to phone_number through the Twilio Messages API, using the {env}/twilio secret (account_sid, auth_token, from_number).

  • phone_number must be exactly 10 digits and is sent as +1<number>; message must be non-empty. Anything else is rejected without a Twilio call.

  • A rejected message or a failed send is reported as a batch item failure. The queue redrives to {env}-user-service-sms-dlq after one failed receive, so a failed code is never resent; the user requests a new one.

  • Every attempt emits the user.sms.send metric with a success tag.

Source: pkg/sms/, cmd/sms-worker/

Validate

  1. phone_number must be 10 digits (400 INVALID_PHONE_NUMBER) and have a record (404 ACCESS_CODE_NOT_FOUND).

  2. A record older than 15 minutes is 400 ACCESS_CODE_EXPIRED.

  3. Five failed guesses lock the code: 403 ACCESS_CODE_LOCKED until a new code is requested.

  4. A wrong code is 400 INVALID_ACCESS_CODE; the failed attempt is persisted with a conditional write (retried up to 3 times on conflict).

  5. A correct code stores a 10-hex-character validation_token and validate_on on the record and returns the token (200).

CreateUser compares its validation_token with the record for the signup phone number; the token is not consumed.

Test Fixtures

In the test environment the three fixed numbers in pkg/api/utils.go (isWaldo) return 201 / 200 immediately without a record or SMS.