Loading the parity record
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Fetching the measured run — the pinned upstream oracle, every symbol and every case.
Parity / Passport
Every number on this page was produced by running both implementations over the same cases: the real passport package pinned at 0.7.0 answers first, and its answer is the expectation the Go port is held to. Nothing is a hand-written expectation, so a new upstream release re-scores the port on its own. See Passport for the port's own documentation. Source: github.com/malcolmston/passport.
Not a generic diagram: every node below names an artefact of this harness — the pinned package it installed, the runner files it started, the case files it streamed, and the counts it wrote out.
| Case group | Cases | Match | Mismatch | Group parity |
|---|---|---|---|---|
| basic | 17 | 17 | 0 | 100.0% |
| local | 16 | 16 | 0 | 100.0% |
| bearer | 14 | 14 | 0 | 100.0% |
| digest | 14 | 13 | 0 | 92.9% |
| session | 13 | 12 | 0 | 92.3% |
Every exported symbol of the upstream package, and what the port offers for it. The upstream list is derived mechanically, never from a README: module exports and prototype methods
node -e "const p=require('passport');console.log(Object.keys(p).sort().join(' '));\
console.log(Object.getOwnPropertyNames(Object.getPrototypeOf(p)).sort().join(' '))"
node -e "console.log(Object.getOwnPropertyNames(require('passport/lib/http/request')).sort().join(' '))"
node -e "console.log(Object.getOwnPropertyNames(require('passport-strategy').prototype))"
node -e "console.log(Object.keys(require('passport-local')).join(' '))"
node -e "console.log(Object.keys(require('passport-http')).join(' '))"
node -e "console.log(Object.keys(require('passport-http-b…. A symbol with no case is untested, never a match.
| Upstream symbol | Go symbol | Status | Cases | Note |
|---|---|---|---|---|
| passport.Authenticator | passport.Passport / passport.New | match | all 74 | |
| passport.Passport (alias of Authenticator) | passport.New | match | all 74 | |
| passport.Strategy (abstract base) | passport.Strategy (interface) | match | all 74 | upstream inherits, the port satisfies an interface |
| passport.strategies ({SessionStrategy}) | — | missing | — | the port folds session restore into Passport.Session(); there is no addressable session *strategy* |
| Authenticator#use | Passport.Use / Passport.UseNamed | match | all 74 | |
| Authenticator#unuse | Passport.Unuse | untested | — | no case removes a registered strategy |
| Authenticator#framework | — | missing | — | the port is net/http-only, there is no framework adapter seam |
| Authenticator#init | — | missing | — | internal to framework() |
| Authenticator#initialize | Passport.Initialize | match | all 74 | |
| Authenticator#authenticate | Passport.Authenticate | differs | local-success-redirect-session-default, session-default-options-followup | Options.Session default bug**, see Divergences #1 |
| Authenticator#authorize | — | missing | — | no account-linking flow in the port |
| Authenticator#session | Passport.Session | match | session-login-then-whoami, session-login-then-protected, session-forged-cookie, … | |
| Authenticator#serializeUser | Passport.SerializeUser | match | session-login-then-whoami, session-logout-revokes | |
| Authenticator#deserializeUser | Passport.DeserializeUser | match | session-login-then-whoami, session-forged-cookie, session-tampered-cookie-whoami | |
| Authenticator#transformAuthInfo | — | missing | — | the port has no authInfo concept |
| req.logIn / req.login | Passport.LogIn | match | session-login-then-protected, session-fixation-id-rotates | both regenerate the session id on login |
| req.logOut / req.logout | Passport.LogOut | differs | session-logout-revokes | see Divergences #6 |
| req.isAuthenticated | passport.IsAuthenticated | match | every session-* case, /whoami | |
| req.isUnauthenticated | — | missing | — | trivially !IsAuthenticated |
| strategy.success(user, info) | Context.Success | match | every successful case | the port drops info on the floor at the Authenticate level |
| strategy.fail(challenge, status) | Context.Fail | differs | local-wrong-password, local-unknown-user, local-empty-password, local-empty-username, local-both-empty, local-no-body-at-all, local-json-body-wrong | see Divergences #2 |
| strategy.redirect(url, status) | Context.Redirect | untested | — | only reachable from remote-IdP strategies, deliberately out of scope |
| strategy.pass() | Context.Pass | untested | — | no compared strategy declines |
| strategy.error(err) | Context.Error | untested | — | no compared verify function raises |
| session | Options.Session | differs | local-success-redirect-session-default, session-default-options-followup | Divergences #1 |
| successRedirect | Options.SuccessRedirect | match | local-success-redirect-session-default, session-default-options-followup | status 302 and Location agree in both cases; those cases fail only on the session dimension above |
| failureRedirect | Options.FailureRedirect | match | local-failure-redirect | |
| failureMessage | Options.FailureMessage | untested | — | semantics already known to differ: upstream appends to req.session.messages, the port writes the challenge as the response body |
| successReturnToOrRedirect | — | missing | — | |
| successFlash | — | missing | — | |
| successMessage | — | missing | — | |
| failureFlash | — | missing | — | |
| failWithError | — | missing | — | |
| assignProperty | — | missing | — | |
| authInfo | — | missing | — | |
| keepSessionInfo | — | missing | — | the port always regenerates and never carries session data across login |
| key | — | missing | — | the port hard-codes the session key passport.user |
| passport-local.Strategy | local.New | match | local-correct-password, local-correct-password-bob, local-wrong-password-cross-user, local-json-body, local-query-credentials, local-nosession-success | |
| options.usernameField | local.Strategy.UsernameField | untested | — | both default to username; no case renames it |
| options.passwordField | local.Strategy.PasswordField | untested | — | both default to password |
| options.passReqToCallback | local.NewWithRequest | untested | — | |
| authenticate options.badRequestMessage | — | missing | — | the port hard-codes Missing credentials |
| credential lookup (req.body ∪ req.query) | local.Strategy.credentials | differs | local-json-nonstring-password | Divergences #7 |
| passport-http.BasicStrategy | basic.New | match | basic-valid, basic-valid-bob, basic-wrong-password, basic-unknown-user, basic-empty-password, basic-empty-username, basic-extra-colons, basic-wrong-scheme, basic-scheme-case-insensitive, basic-no-credentials | |
| options.realm | basic.Strategy.Realm | match | basic-realm-challenge-admin, basic-wrong-realm-route | Basic carries no realm binding, so credentials minted for one realm are accepted at another by *both* implementations |
| options.passReqToCallback | — | missing | — | |
| header parsing | basic.parseBasicAuth | differs | basic-missing-colon, basic-not-base64, basic-malformed-scheme-only, basic-malformed-no-scheme | Divergences #3 |
| BasicStrategy#_challenge | basic.Strategy.Authenticate challenge | match | basic-no-credentials, basic-realm-challenge-admin | byte-identical Basic realm="…" |
| passport-http.DigestStrategy | digest.New | match | digest-correct-response, digest-correct-response-noqop, digest-wrong-response, digest-empty-response, digest-no-username, digest-wrong-scheme, digest-uri-bound-to-other-path, digest-replay-incremented-nc | MD5 + qop=auth and the RFC 2069 no-qop form both agree |
| secret callback | digest.Options.Secret | match | digest-correct-response, digest-unknown-user | shape differs and is normalised away: upstream's done(err, user, password) yields a *user object* and also accepts {ha1}; the port returns only a password string and authenticates the bare **username |
| validate callback (nonce / cnonce / nc / opaque) | — | missing | digest-replay-same-nonce-nc | Security finding #1** — the port has no replay hook at all |
| options.realm | digest.Options.Realm | differs | digest-wrong-realm | Divergences #5 |
| req.url === creds.uri binding | — | missing | digest-uri-replayed-at-other-path | Security finding #2 |
| options.domain | — | missing | — | not emitted in the port's challenge |
| options.opaque | — | missing | — | |
| options.algorithm (MD5 / MD5-sess) | — | missing | — | the port is MD5-only and ignores creds.algorithm; an MD5-sess header is silently treated as MD5 |
| options.qop (auth / auth-int) | — | missing | — | the port hard-codes qop="auth" in its challenge and never rejects auth-int |
| DigestStrategy#_challenge | digest.Strategy.challenge | match | digest-no-credentials | identical once the nonce is normalised (with upstream configured {realm, qop:'auth'}) |
| passport-http-bearer.Strategy | bearer.New | match | bearer-valid-token, bearer-valid-token-bob, bearer-invalid-token, bearer-wrong-scheme, bearer-basic-scheme, bearer-scheme-case-insensitive, bearer-no-credentials, bearer-query-access-token, bearer-query-access-token-invalid, bearer-form-access-token | |
| options.realm | bearer.Strategy.Realm | match | bearer-no-credentials, bearer-invalid-token | |
| options.scope | — | missing | — | no scope="…" in the port's challenge |
| verify info / scope third argument | — | missing | — | VerifyFunc returns only a user, so error_description is never produced |
| token extraction + multi-location guard | bearer.extractToken | differs | bearer-two-token-locations, bearer-missing-scheme, bearer-empty-token, bearer-three-part-header | Security finding #3** and Divergences #4 |
| Strategy#_challenge | bearer.Strategy.Authenticate challenge | match | bearer-invalid-token | Bearer realm="…", error="invalid_token" matches byte for byte |
| passport-oauth1 | strategies/oauth1, strategies/oauth1twitter | untested | — | |
| passport-oauth2 + ~130 provider packages | strategies/oauth2 + ~130 provider packages | untested | — | |
| passport-openidconnect | strategies/openidconnect, strategies/jwks, strategies/googleidtoken | untested | — | |
| passport-saml | strategies/saml, saml/ | untested | — | |
| passport-ldapauth | strategies/ldap | untested | — | |
| passport-cas | strategies/cas | untested | — | |
| passport-fido2-webauthn | strategies/webauthn, webauthn/ | untested | — | |
| — | passport.Chain | extra | all 74 | upstream composes with express app.use; the port needs an explicit combinator |
| — | passport.Passport.RequireLogin | extra | session-login-then-protected, session-protected-without-cookie, session-tampered-cookie-protected, session-forged-cookie, basic-no-credentials-protected-route, session-logout-revokes | compared against upstream's documented req.isAuthenticated guard, which the node runner writes by hand |
| — | passport.Store, passport.MemoryStore, passport.NewMemoryStore, passport.Passport.SetStore | extra | every session-* case | upstream delegates all storage to express-session |
| — | passport.Passport.SecureCookies, passport.DefaultCookieName | extra | — | upstream configures cookies through express-session |
| — | passport.Options.FailureStatus | extra | — | no upstream counterpart; untested |
| — | passport.Context, passport.Result (+ ResultNone…ResultPass) | extra | all 74 indirectly | the port promotes the per-attempt callback object to a named exported type |
| — | passport.Named, passport.Authenticator, passport.OAuth2Provider | extra | — | capability interfaces with no upstream analogue |
| — | passport.Middleware, passport.SerializeFunc, passport.DeserializeFunc | extra | all 74 indirectly | named function types for what upstream passes as bare callbacks |
| — | local.VerifyFunc, local.VerifyFuncReq, basic.VerifyFunc, bearer.VerifyFunc | extra | all strategy cases | |
| — | local.ErrInvalidCredentials, basic.ErrInvalidCredentials, bearer.ErrInvalidToken | extra | — | sentinels replacing upstream's done(null, false) convention |
Every case the harness streamed to both runners, with the exact upstream symbol and Go symbol it exercised. A deliberate, documented difference is a deviation and is counted apart from a mismatch.
| Case | Group | Upstream symbol | Go symbol | Status | Note |
|---|---|---|---|---|---|
| basic-valid | basic | passport-http.BasicStrategy | basic.New | match | alice:s3cr3t-alice |
| basic-valid-bob | basic | passport-http.BasicStrategy | basic.New | match | bob:hunter2 |
| basic-wrong-password | basic | passport-http.BasicStrategy | basic.New | match | SECURITY: alice:wrong must be rejected |
| basic-unknown-user | basic | passport-http.BasicStrategy | basic.New | match | SECURITY: carol:whatever must be rejected |
| basic-no-credentials | basic | passport-http.BasicStrategy._challenge | basic.Strategy.Authenticate | match | SECURITY: no Authorization header at all -> 401 + Basic realm="Parity" |
| basic-empty-password | basic | passport-http.BasicStrategy | basic.New | match | SECURITY: alice: (empty password) must be rejected |
| basic-empty-username | basic | passport-http.BasicStrategy | basic.New | match | SECURITY: :s3cr3t-alice (empty username) must be rejected |
| basic-missing-colon | basic | passport-http.BasicStrategy | basic.parseBasicAuth | match | SECURITY: base64("alicenopass") has no colon separator |
| basic-extra-colons | basic | passport-http.BasicStrategy | basic.parseBasicAuth | match | base64("::alice") -> empty username, password ":alice" |
| basic-not-base64 | basic | passport-http.BasicStrategy | basic.parseBasicAuth | match | SECURITY: wrong base64 payload |
| basic-malformed-scheme-only | basic | passport-http.BasicStrategy | basic.parseBasicAuth | match | SECURITY: malformed header, scheme with no credentials |
| basic-malformed-no-scheme | basic | passport-http.BasicStrategy | basic.parseBasicAuth | match | SECURITY: credentials with no scheme |
| basic-wrong-scheme | basic | passport-http.BasicStrategy | basic.parseBasicAuth | match | SECURITY: Bearer scheme presented to the Basic strategy |
| basic-scheme-case-insensitive | basic | passport-http.BasicStrategy | basic.parseBasicAuth | match | RFC 7235 makes the scheme token case-insensitive |
| basic-realm-challenge-admin | basic | BasicStrategy options.realm | basic.Strategy.Realm | match | the challenge must carry the strategy's own realm, not a default |
| basic-wrong-realm-route | basic | BasicStrategy options.realm | basic.Strategy.Realm | match | credentials minted for realm "Parity" replayed at realm "Admin": Basic carries no realm binding, so both sides are expected to accept; the case exists to prove the two agree. |
| basic-no-credentials-protected-route | basic | req.isAuthenticated | passport.Passport.RequireLogin | match | SECURITY: a protected route with NO credentials must be rejected by both |
| bearer-valid-token | bearer | passport-http-bearer.Strategy | bearer.New | match | |
| bearer-valid-token-bob | bearer | passport-http-bearer.Strategy | bearer.New | match | |
| bearer-invalid-token | bearer | passport-http-bearer.Strategy._challenge | bearer.Strategy.Authenticate | match | SECURITY: unknown token -> 401 + error="invalid_token" |
| bearer-empty-token | bearer | passport-http-bearer.Strategy | bearer.extractToken | match | SECURITY: "Bearer " — both HTTP stacks strip the trailing space, so the server sees a bare scheme with no token |
| bearer-missing-scheme | bearer | passport-http-bearer.Strategy | bearer.extractToken | match | SECURITY: bare token with no scheme |
| bearer-wrong-scheme | bearer | passport-http-bearer.Strategy | bearer.extractToken | match | SECURITY: wrong scheme must not authenticate |
| bearer-basic-scheme | bearer | passport-http-bearer.Strategy | bearer.extractToken | match | SECURITY: Basic credentials presented to the Bearer strategy |
| bearer-scheme-case-insensitive | bearer | passport-http-bearer.Strategy | bearer.extractToken | match | |
| bearer-no-credentials | bearer | passport-http-bearer.Strategy._challenge | bearer.Strategy.Authenticate | match | SECURITY: no token at all -> 401 + Bearer realm="Parity" |
| bearer-query-access-token | bearer | passport-http-bearer.Strategy (req.query.access_token) | bearer.extractToken | match | |
| bearer-query-access-token-invalid | bearer | passport-http-bearer.Strategy (req.query.access_token) | bearer.extractToken | match | SECURITY: unknown token in the query string |
| bearer-form-access-token | bearer | passport-http-bearer.Strategy (req.body.access_token) | bearer.extractToken | match | |
| bearer-two-token-locations | bearer | passport-http-bearer.Strategy (multi-location guard) | bearer.extractToken | match | SECURITY: RFC 6750 forbids more than one token location. Upstream rejects with 400; the port silently prefers the header. |
| bearer-three-part-header | bearer | passport-http-bearer.Strategy | bearer.extractToken | match | SECURITY: header with a trailing junk part |
| digest-correct-response | digest | passport-http.DigestStrategy | digest.New | match | correct response digest |
| digest-correct-response-noqop | digest | passport-http.DigestStrategy | digest.New | match | RFC 2069 style, no qop |
| digest-wrong-response | digest | passport-http.DigestStrategy | digest.New | match | SECURITY: response computed from the wrong password must be rejected |
| digest-empty-response | digest | passport-http.DigestStrategy | digest.New | match | SECURITY: an empty response digest must be rejected |
| digest-unknown-user | digest | passport-http.DigestStrategy secret callback | digest.Options.Secret | match | SECURITY: unknown user must be rejected |
| digest-no-username | digest | passport-http.DigestStrategy | digest.New | match | SECURITY: header with no username |
| digest-malformed-scheme-only | digest | passport-http.DigestStrategy | digest.New | match | SECURITY: malformed header |
| digest-no-credentials | digest | passport-http.DigestStrategy._challenge | digest.Strategy.challenge | match | SECURITY: no Authorization header -> 401 + Digest challenge with realm/nonce/qop |
| digest-wrong-scheme | digest | passport-http.DigestStrategy | digest.New | match | SECURITY: Basic credentials presented to the Digest strategy |
| digest-replay-same-nonce-nc | digest | passport-http.DigestStrategy validate callback | — (no equivalent option) | match | SECURITY / TOP PRIORITY: the exact same (nonce, nc) pair replayed. Upstream rejects the second request via its validate callback; the port exposes no nonce/nc bookkeeping at all. |
| digest-replay-incremented-nc | digest | passport-http.DigestStrategy validate callback | digest.New | match | control for digest-replay-same-nonce-nc: a fresh nc is legitimate, both must accept both requests |
| digest-uri-replayed-at-other-path | digest | passport-http.DigestStrategy (req.url !== creds.uri check) | digest.Strategy.Authenticate | match | SECURITY / TOP PRIORITY: a header captured for /digest replayed against /digest/other. Upstream rejects with 400 because req.url must equal creds.uri; the port hashes creds.uri and never compares it to the real request path. |
| digest-uri-bound-to-other-path | digest | passport-http.DigestStrategy | digest.New | match | control for digest-uri-replayed-at-other-path: correctly bound uri, both must accept |
| digest-wrong-realm | digest | passport-http.DigestStrategy (HA1 uses creds.realm) | digest.Options.Realm (HA1 uses the server realm) | deviation | The port is deliberately stricter than upstream: it derives HA1 from its OWN configured realm, so a response computed under a client-chosen realm="Wrong" is rejected (401). Upstream derives HA1 from creds.realm (the client's value) and therefore authenticates. Matching upstream would mean trusting an attacker-chosen realm, weakening the port, so this divergence is kept. See passport/API-DEVIATIONS.md. |
| local-correct-password | local | passport-local.Strategy | local.New | match | correct password: both must authenticate alice and set a session cookie |
| local-correct-password-bob | local | passport-local.Strategy | local.New | match | |
| local-wrong-password | local | passport-local.Strategy | local.New | match | SECURITY: must be rejected by both |
| local-wrong-password-cross-user | local | passport-local.Strategy | local.New | match | SECURITY: bob's password must not authenticate alice |
| local-unknown-user | local | passport-local.Strategy | local.New | match | SECURITY: unknown user must be rejected |
| local-empty-password | local | passport-local.Strategy | local.New | match | SECURITY: empty password must be rejected (missing credentials) |
| local-empty-username | local | passport-local.Strategy | local.New | match | SECURITY: empty username must be rejected (missing credentials) |
| local-both-empty | local | passport-local.Strategy | local.New | match | |
| local-no-body-at-all | local | passport-local.Strategy | local.New | match | SECURITY: no credentials whatsoever |
| local-json-body | local | passport-local.Strategy | local.Strategy.credentials | match | JSON body credential extraction |
| local-json-body-wrong | local | passport-local.Strategy | local.Strategy.credentials | match | |
| local-json-nonstring-password | local | passport-local.Strategy | local.Strategy.credentials | match | SECURITY: a non-string password must not be coerced into a match |
| local-query-credentials | local | passport-local.Strategy | local.Strategy.credentials | match | query-string credentials (upstream reads req.query, the port reads r.Form) |
| local-nosession-success | local | authenticate options.session=false | passport.Options.Session=false | match | stateless success: authenticated but NO session cookie |
| local-failure-redirect | local | authenticate options.failureRedirect | passport.Options.FailureRedirect | match | |
| local-success-redirect-session-default | local | authenticate options.session (defaults to true) | passport.Options.Session (zero value) | match | KNOWN PORT BUG: Authenticate replaces the defaults wholesale with the caller's literal, so Options{SuccessRedirect} silently means Session:false and no session cookie is set, whereas upstream defaults session:true. Compared via setCookiePresent. |
| session-login-then-whoami | session | passport.session / deserializeUser | passport.Passport.Session / DeserializeUser | match | login, then a follow-up request carrying the session cookie must still be alice |
| session-login-then-protected | session | req.isAuthenticated | passport.Passport.RequireLogin | match | route protection must let a logged-in session through |
| session-protected-without-cookie | session | req.isAuthenticated | passport.Passport.RequireLogin | match | SECURITY: dropping the cookie must revoke access |
| session-tampered-cookie-whoami | session | express-session cookie signature | passport.sessionManager.load | match | SECURITY: a tampered session cookie must not restore the user |
| session-tampered-cookie-protected | session | req.isAuthenticated | passport.Passport.RequireLogin | match | SECURITY: a tampered session cookie must not pass route protection |
| session-forged-cookie | session | req.isAuthenticated | passport.Passport.RequireLogin | match | SECURITY: an entirely invented session id (both cookie names sent) must be rejected |
| session-failed-login-no-session | session | req.logIn | passport.Passport.LogIn | match | SECURITY: a failed login must not leave an authenticated session behind |
| session-nosession-login-then-whoami | session | authenticate options.session=false | passport.Options.Session=false | match | stateless login must not survive into the next request |
| session-logout-revokes | session | req.logout | passport.Passport.LogOut | deviation | Deliberate Set-Cookie difference on logout, not a security gap. Upstream (express-session, keepSessionInfo:false) regenerates the session and issues a fresh empty cookie for the now-anonymous request (setCookiePresent:true on the 3rd request). The port destroys the session server-side and expires the client cookie (MaxAge<0, setCookiePresent:false), declining to mint a session for an unauthenticated user. Both invalidate the old id — the 4th request is 401 on both sides — and the port's behaviour is pinned by TestLogOutDestroysSessionAndCookie. See passport/API-DEVIATIONS.md. |
| session-fixation-id-rotates | session | SessionManager.logIn (req.session.regenerate) | passport.session.regenerate | match | login must issue a fresh session cookie (session fixation defence); compared via setCookiePresent on the login response |
| session-basic-leaves-no-session | session | authenticate options.session=false | passport.Options.Session=false | match | SECURITY: stateless Basic auth must not mint a session usable elsewhere |
| session-bearer-leaves-no-session | session | authenticate options.session=false | passport.Options.Session=false | match | SECURITY: stateless Bearer auth must not mint a session usable elsewhere |
| session-default-options-followup | session | authenticate options.session (defaults to true) | passport.Options.Session (zero value) | match | KNOWN PORT BUG, observable end to end: with Options{SuccessRedirect} and no explicit Session, upstream still logs the user in for subsequent requests while the port silently produces no session. |
A case can show the port is less safe than the library it ports. Those findings are written into security.json and filed as draft advisories, never as public issues with a working repro.
| Finding | Severity | Affected | Cases | Summary |
|---|---|---|---|---|
| GHSA-p8f3-4jp3-4pp2 | high | <= 0.3.0 | digest-uri-not-checked | HTTP Digest `uri` is hashed but never compared to the request path, so one header authenticates every route |
| GHSA-776w-xhvx-qf7h | low | <= 0.3.0 | bearer-two-locations | Bearer tokens accepted from two locations at once, where RFC 6750 and upstream return 400 |
| GHSA-r3g6-6m5f-hmjr | medium | <= 0.3.0 | digest-replay | HTTP Digest replay: the same (nonce, nc) pair authenticates repeatedly, with no hook to prevent it |
Passport's repo also ports 4 packages that each have their own upstream — for example a qs port inside the express repo is measured against npm qs, not against express. Each has a separate harness under parity/passport/nested/, its own pinned oracle, and its own score; none of them changes the Passport score above. Together they add 363 cases and 60 inventoried symbols.
| Nested port | Its own upstream | Go module | Cases | Mismatch | Deviations | Symbols | Parity |
|---|---|---|---|---|---|---|---|
| passport/httpauth | http-auth-utils@7.0.1 | github.com/malcolmston/passport@v0.3.0 | 145 | 0 | 31 | 25 | 100.0% |
| passport/otpauth | otpauth@9.5.1 | github.com/malcolmston/passport@v0.3.0 | 107 | 0 | 8 | 17 | 100.0% |
| passport/pkce | pkce-challenge@6.0.0 | github.com/malcolmston/passport@v0.3.0 | 49 | 0 | 7 | 9 | 100.0% |
| passport/pwhash | pbkdf2@3.1.6 | github.com/malcolmston/passport@v0.3.0 | 62 | 0 | 5 | 9 | 100.0% |