Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
69 lines
1.7 KiB
Go
69 lines
1.7 KiB
Go
// Package zxcvbnjs wraps @zxcvbn-ts/core via goja (ExecJS-style) for exact
|
|
// parity between frontend and backend password strength scoring.
|
|
//
|
|
// The bundled JS contains the same @zxcvbn-ts/core library used in the
|
|
// SvelteKit frontend, so scores are identical for any given password.
|
|
package zxcvbnjs
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
|
|
"github.com/dop251/goja"
|
|
)
|
|
|
|
//go:embed bundle.js
|
|
var bundleFS embed.FS
|
|
|
|
var (
|
|
vm *goja.Runtime
|
|
vmOnce sync.Once
|
|
vmErr error
|
|
)
|
|
|
|
func getVM() (*goja.Runtime, error) {
|
|
vmOnce.Do(func() {
|
|
vm = goja.New()
|
|
bundleData, err := bundleFS.ReadFile("bundle.js")
|
|
if err != nil {
|
|
vmErr = fmt.Errorf("failed to read zxcvbn bundle: %w", err)
|
|
return
|
|
}
|
|
_, err = vm.RunScript("zxcvbn-bundle.js", string(bundleData))
|
|
if err != nil {
|
|
vmErr = fmt.Errorf("failed to evaluate zxcvbn bundle: %w", err)
|
|
return
|
|
}
|
|
})
|
|
return vm, vmErr
|
|
}
|
|
|
|
// Score returns the zxcvbn-ts score (0-4) for the given password.
|
|
// It uses the exact same @zxcvbn-ts/core library as the frontend,
|
|
// guaranteeing identical results for the same input.
|
|
func Score(password string) (int, error) {
|
|
runtime, err := getVM()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("zxcvbnjs init error: %w", err)
|
|
}
|
|
|
|
zxcvbnScore, ok := goja.AssertFunction(runtime.Get("zxcvbnScore"))
|
|
if !ok {
|
|
return 0, fmt.Errorf("zxcvbnScore not found in JS runtime")
|
|
}
|
|
|
|
result, err := zxcvbnScore(goja.Undefined(), runtime.ToValue(password))
|
|
if err != nil {
|
|
log.Printf("zxcvbnjs: eval error: %v", err)
|
|
return 0, fmt.Errorf("zxcvbnScore eval failed: %w", err)
|
|
}
|
|
|
|
score := int(result.ToInteger())
|
|
if score < 0 || score > 4 {
|
|
return 0, fmt.Errorf("unexpected zxcvbn score: %d", score)
|
|
}
|
|
return score, nil
|
|
}
|