feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s

Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
Codex Local
2026-10-08 10:06:44 +08:00
parent 98fb2965d0
commit bf1233d5df
861 changed files with 94614 additions and 3209 deletions
@@ -0,0 +1,67 @@
package onboarding
import (
"context"
"net/http"
"time"
"github.com/jackc/pgx/v5"
)
const trustedLoginLifetime = 30 * 24 * time.Hour
func (s *Service) loginRealm() string {
if s.operatorLogin {
return "operator"
}
return "customer"
}
func (s *Service) trustedLoginCookieName() string {
name := "platform-" + s.loginRealm() + "-trusted-device"
if !s.options.AllowLocalHTTP {
name = "__Host-" + name
}
return name
}
func (s *Service) trustedLoginCookie(r *http.Request) string {
if c, err := r.Cookie(s.trustedLoginCookieName()); err == nil && validB64(c.Value, 32) {
return c.Value
}
return ""
}
// Only a successfully verified, single-use SMS can establish device trust.
// Bind it to the exact credential so an administrative password reset also
// invalidates previously trusted devices without needing a separate revocation.
func (s *Service) rememberLoginDevice(ctx context.Context, tx pgx.Tx, phoneHash string) (string, error) {
var encoded string
if err := tx.QueryRow(ctx, "SELECT COALESCE(password_hash,'') FROM "+s.phoneCredentialTable()+" WHERE phone_digest=$1 FOR SHARE", phoneHash).Scan(&encoded); err != nil {
return "", err
}
token := randomToken(32)
_, err := tx.Exec(ctx, `INSERT INTO trusted_login_device(token_hash,realm,phone_digest,credential_digest,verified_at,expires_at) VALUES($1,$2,$3,$4,$5,$6)`, digest([]byte(token)), s.loginRealm(), phoneHash, digest([]byte(encoded)), s.now(), s.now().Add(trustedLoginLifetime))
return token, err
}
func (s *Service) requireTrustedLoginDevice(ctx context.Context, tx pgx.Tx, phoneHash, encoded, token string) error {
trusted := false
if validB64(token, 32) {
err := tx.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM trusted_login_device WHERE token_hash=$1 AND realm=$2 AND phone_digest=$3 AND credential_digest=$4 AND expires_at>$5 AND revoked_at IS NULL)`, digest([]byte(token)), s.loginRealm(), phoneHash, digest([]byte(encoded)), s.now()).Scan(&trusted)
if err != nil {
return err
}
}
if !trusted {
return fail(403, "NEW_DEVICE_SMS_REQUIRED", "首次在此设备登录,请使用手机验证码完成验证")
}
return nil
}
func (s *Service) updateTrustedDevicesAfterPassword(ctx context.Context, tx pgx.Tx, phoneHash, oldHash, newHash, currentToken string) error {
// Retain only an existing, unexpired SMS verification on this browser. A
// password change must never turn an unverified browser into a trusted one.
_, err := tx.Exec(ctx, `UPDATE trusted_login_device SET credential_digest=CASE WHEN token_hash=$3 AND credential_digest=$4 AND expires_at>$6 THEN $5 ELSE credential_digest END,revoked_at=CASE WHEN token_hash=$3 AND credential_digest=$4 AND expires_at>$6 THEN NULL ELSE $6 END WHERE realm=$1 AND phone_digest=$2 AND revoked_at IS NULL`, s.loginRealm(), phoneHash, digest([]byte(currentToken)), digest([]byte(oldHash)), digest([]byte(newHash)), s.now())
return err
}