Developer notes
Overview · Installation · Configuration · Usage · FAQ
How a sign-in works
Section titled “How a sign-in works”sendnormalizes the number to E.164 (Phone::normalize(), default calling code from the country select or the settings), checks the allowed countries and the rate limits, creates a challenge and sends the code through the providers of the delivery setting (WhatsApp first, SMS as fallback when both are selected).- The challenge is bound to the visitor’s session: a random 32-byte token lives in the session, the database keeps only its SHA-256. The code is stored as HMAC-SHA256 (token hash + code, keyed with a secret derived from the store’s
crypt_key) and compared withhash_equals(). verify_codecounts wrong attempts (the challenge dies at the limit), checks expiry, then looks up the account: the verified number first (?:kv_pol_phones), then (setting) numbers typed in customer profiles, normalized in PHP. Exactly one active customer →fn_login_user($user_id, true)(the same call as the core e-mail one-time password: new session ID, cart merge, login hooks). None → registration step (if allowed). Several → refused.registeris accepted for 15 minutes after a confirmed code, once. The account is created withfn_update_user()(random password, the number as profile phone, e-mail notification only when an e-mail was given) and signed in.
Answers before a valid code never depend on whether an account exists. All endpoints are POST + AJAX and CSRF-checked (schemas/security/csrf_validation.post.php). Return URLs are limited to the store’s own hosts.
Data model
Section titled “Data model”| Table | Content |
|---|---|
?:kv_pol_phones |
user_id (PK), phone (E.164), verified_at |
?:kv_pol_challenges |
token hash, purpose (L sign-in/registration, V profile verification), number, user, storefront, code hash, attempts, sends, channel, created/expires/last sent/verified times, status (P pending, V verified, U used, X void). Purged one day after expiry. |
?:kv_pol_log |
time, event, keyed hash and masked form of the number, IP, user, storefront, channel, provider, purpose, message. Rate limits count rows of this table. Purged after the retention period. |
Controllers and endpoints
Section titled “Controllers and endpoints”| Area | Dispatch | Purpose |
|---|---|---|
| Storefront | kv_phone_otp_login.login (GET) |
Sign-in / registration page (return_url supported) |
| Storefront | kv_phone_otp_login.verify (GET) |
Profile verification page (signed-in customers) |
| Storefront | kv_phone_otp_login.send, .resend, .verify_code, .register (POST, AJAX) |
JSON answer in kv_pol: ok, step (phone, code, register, done), error + message, redirect_url |
| Storefront | init.post.php, profiles.post.php |
Assign the kv_pol flags (same for every visitor) and the profile panel data |
| Admin | kv_phone_otp_login.log (GET), .test_send (POST) |
Sign-in log, provider test. Privileges: view_users / manage_users. |
Template hooks (responsive theme): index:login_buttons (post: the button), profiles:account_update (post: profile panel / registration link).
PHP hooks: update_profile (drops the verified record when the profile phone changes to another number), post_delete_user (removes the record). Both have the same signature in 4.17.2 and 4.21.2.
Providers
Section titled “Providers”Messaging\ProviderInterface with TestProvider, TwilioProvider (POST /2010-04-01/Accounts/{sid}/Messages.json), GenericHttpProvider and WhatsAppCloudProvider (POST /{version}/{phone-number-id}/messages, template with a body parameter and, optionally, a URL button parameter). Providers never throw; they return a Result (ok, transient, HTTP status, message ID, error without credentials). Messaging\Http uses cURL with 5 s connect / 10 s total timeouts. For local development a mock base URL can be set with the constant KV_PHONE_OTP_LOGIN_API_BASE (never in production). One-time codes are not queued: a code is sent at once, and the fallback channel is the retry.
Full page cache (4.21)
Section titled “Full page cache (4.21)”The button under the login forms depends only on the settings and the license, so cached pages are the same for every visitor. The phone pages are not cacheable dispatches, and every state change is an AJAX POST. Nothing is written on storefront GET requests.
License check
Section titled “License check”Tygh\Addons\KvPhoneOtpLogin\License\Client (generated) verifies the license. Gate points: Config::isActive() (button and profile panel output, the phone pages, send and resend). Not gated: verify_code and register of a code that was already sent, so a started sign-in can always be finished.
Not in 1.0.0
Section titled “Not in 1.0.0”Two-factor authentication for administrators or vendors, phone verification required at checkout, numbers of billing/shipping profiles, voice calls, admin editing of a customer’s verified number.
File map
Section titled “File map”| Path | Purpose |
|---|---|
app/addons/kv_phone_otp_login/addon.xml |
Settings, tables |
init.php, func.php |
Hooks, settings variants (countries), storefront flags, fn_kv_phone_otp_login_is_licensed() |
Tygh/Addons/KvPhoneOtpLogin/Service.php |
The code flow: send, resend, verify, register, admin test send |
Accounts.php |
Account lookup by number, verified numbers, registration |
Config.php, Phone.php, Log.php |
Settings with limits, E.164 and calling codes, log and rate-limit counters |
Messaging/ |
Providers, HTTP client, results |
License/, controllers/backend/kv_phone_otp_login_license.php |
License check (generated) |
Upgrade/Synchronizer.php |
Self-healing upgrades (generated) |
controllers/frontend/, controllers/backend/ |
Pages and endpoints |
schemas/ |
Menu, permissions, CSRF validation |
var/themes_repository/responsive/templates/addons/kv_phone_otp_login/ |
Widget, page and hook templates |
js/addons/kv_phone_otp_login/func.js |
The storefront widget (jQuery, no external libraries) |