npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zevra/bureau-connexion

v0.1.1

Published

Connexion des applications de bureau Zevra par le navigateur du système (RFC 8252 + PKCE S256) : contrat, routes serveur, boucle locale côté app.

Readme

@zevra/bureau-connexion

Connexion des applications de bureau Zevra (Zevra Admin, ZOS, lexform) au Compte Zevra par le navigateur du système — RFC 8252 « OAuth 2.0 for Native Apps » + PKCE S256 + boucle locale 127.0.0.1. L'app n'affiche jamais l'IdP : la personne s'authentifie dans son navigateur (lien magique, clé d'accès, Google — tout y marche), le site rend un code à usage unique à l'app, l'app l'échange contre sa session.

| Morceau | Import | Rôle | |---|---|---| | Contrat | @zevra/bureau-connexion | Formes, PKCE, empreintes, URL, code de vérification, validation des réponses. Pur (node:crypto). | | Navigateur | @zevra/bureau-connexion/navigateur | Ce qu'un composant client peut importer (sans node:) : chemins, redirectionLocale, destinationInterne, délai d'armement. | | Serveur | @zevra/bureau-connexion/serveur | creerRoutesAppairage(adaptateur) → les deux routes (Request → Response). | | Coquille | @zevra/bureau-connexion/coquille | lancerAppairage(...) : boucle locale, navigateur, échange — Electron injecté. |

Le déroulé

  1. L'app écoute 127.0.0.1:<port éphémère> et ouvre dans le navigateur <site>/desktop/appairer?port&defi&etat. Elle affiche un code de vérification court (ABC-DEF), dérivé de l'état.
  2. La page exige une session (sinon la connexion du site → le SSO, puis retour ici), montre le compte, le même code, et un bouton armé après 500 ms.
  3. Au clic : POST /api/desktop/appairer enregistre l'empreinte d'un code à usage unique, lié à la session du navigateur, et rend { redirection } vers la boucle locale. La page y part (location.replace).
  4. L'app compare l'état à temps constant, puis POST /api/desktop/echanger { code, verifieur } depuis une partition jetable. Le serveur consomme le code d'abord (brûlé même si le vérifieur est faux), vérifie PKCE, vérifie que la session du navigateur existe encore, puis l'adaptateur crée la session de l'app (cookie signé) ou son jeton.

Ce que la relecture de ZOS (03/10/2026) a imposé, et qui est ici d'office

  • code lié à la session du navigateur → déconnecté de la plateforme, plus rien ne s'échange (constat 1) ;
  • code de vérification des deux côtés + bouton armé en différé → la confirmation est liée à l'instance d'app qui a demandé (constat 2) ;
  • destinationInterne en « résoudre puis comparer », contrôles refusés (constat 3) ;
  • la session de bureau se reconnaît par un lien en base (l'adaptateur le garde dans delivrer), jamais par un User-Agent recopié ;
  • ordre de livraison : le site du canal doit servir /desktop/appairer, /desktop/connecte, /api/desktop/appairer, /api/desktop/echanger et sa migration AVANT qu'une version de l'app en dépende (constat 5).

Brancher une app

La table (une par app, dans son schéma)

desktop_appairages
  id              uuid pk
  user_id         (type de la table user)      fk user  on delete cascade
  session_id      (type de la table session)   fk session on delete cascade   ← la session du NAVIGATEUR
  code_hache      text unique
  defi            text
  contexte        jsonb null                   (organisation active, équipe…)
  expire_le       timestamptz
  utilise_le      timestamptz null
  session_creee_id (type de session) null      fk session on delete set null  ← la session de L'APP, pour la reconnaître
  created_at      timestamptz

Le serveur (Next)

// src/lib/appairage-bureau.ts
import { creerRoutesAppairage } from '@zevra/bureau-connexion/serveur'
export const routesAppairage = creerRoutesAppairage({
  appUrl: ORIGINE_PUBLIQUE,
  session: async (req) => { const s = await auth.api.getSession({ headers: req.headers }); return s && { id: s.session.id, userId: s.user.id, estBureau: estSessionBureau(s.session.id) } },
  enregistrer: async (d) => { purge(); insert(d) },
  consommer: async (h) => update … where code_hache = h and utilise_le is null and expire_le > now() returning …,
  sessionNavigateurValide: async (id) => exists session where id and expiresAt > now(),
  delivrer: async (d) => { const s = insertSession(d.userId, …); lierSessionCreee(d, s.id); return { cookie: descriptionCookie(ctx.authCookies.sessionToken, valeurCookieSession(s.token, ctx.secret), s.expiresAt) } },
  audit: (e) => …,
})
// app/api/desktop/appairer/route.ts  → export const POST = (req) => routesAppairage.appairer(req)
// app/api/desktop/echanger/route.ts  → export const POST = (req) => routesAppairage.echanger(req)

La page /desktop/appairer (serveur) : lireDemandeAppairage(searchParams) → sans session, redirect(<connexion>?redirect=${encodeURIComponent(cheminAppairage(d))}) → affiche le compte et codeVerification(d.etat) → le composant client POSTe { port, defi, etat } après DELAI_ARMEMENT_MS, puis location.replace(redirectionLocale(...)). Une page /desktop/connecte (« c'est fait » / « annulé ») reçoit le navigateur.

La coquille (Electron, processus principal)

import { lancerAppairage } from '@zevra/bureau-connexion/coquille'
const t = await lancerAppairage({
  origine: URL_SITE,
  resultat: 'cookie',                       // 'jeton' pour une app à Bearer (lexform)
  ouvrirNavigateur: (u) => shell.openExternal(u).then(() => true, () => false),
  fetch: session.fromPartition('persist:x-echange', { cache: false }).fetch,
  surEvenement: (e) => { if (e.type === 'attente') afficherCode(e.codeVerification) },
})
const issue = await t.issue                 // { type:'cookie', cookie } | { type:'jeton', jeton } | { type:'echec', raison }
if (issue.type === 'cookie') await session.fromPartition(PARTITION).cookies.set({ url: URL_SITE, name: issue.cookie.nom, value: issue.cookie.valeur, expirationDate: issue.cookie.expirationS, secure: issue.cookie.secure, httpOnly: true, sameSite: issue.cookie.sameSite })

Jamais de déclenchement automatique sans geste ; une tentative à la fois ; t.annuler() sur « Annuler » et au départ de l'app.

Ce que tiennent les tests (pnpm test)

Formes strictes (port chaîne de chiffres, défi/état/code/vérifieur), PKCE à temps constant, code de vérification stable et lisible, destinationInterne contre /\t/evil.example, lireRetour (Host, doublons, forme absolue), origine égale (jamais same-site), limiteur ; serveur : parcours nominal cookie et jeton, autre origine refusée, session d'app refusée, rejouer refusé, vérifieur faux brûle le code, session du navigateur disparue refuse, délivrance refusée uniforme, 400/429/503 ; coquille (vraie boucle locale) : état faux refusé sans fermer, Host étranger refusé, annulation, refus/injoignable/session, délai, navigateur impossible, jeton, journal sans secret.