Documentation

Donnez aux serveurs et tâches CI leur propre accès

Un client de service est un serveur, un script ou un travail CI qui travaille sur un projet avec ses propres identifiants. Il se connecte avec une clé privée ou un secret, reçoit un jeton d’accès de 15 minutes, et n’a jamais besoin qu’une personne approuve chaque exécution.

Ce qu’est un client de service

  • Appartient au projet. Le projet conserve le client lorsque la personne ou l’agent qui l’a créé part. Ses requêtes sont facturées comme des requêtes d’agent, contre les mêmes requêtes incluses.
  • Lecture seule, Proposer ou Gérer. Défini à la création, comme un agent connecté. Lecture seule liste et lit ; Proposer téléverse aussi des médias et propose des modifications de playlists et d’écrans pour qu’une personne les révise ; Gérer modifie, publie et dépense aussi. Lorsque le projet exige une révision pour les modifications des agents, un client travaille au maximum en mode Proposer. Voir Lecture seule, Proposer et Gérer.
  • Capacités. N’importe lequel de screens, content, playlists, advertising, reports et project, dans celles du créateur.
  • Limité. Un client de service ne crée ni ne gère jamais de clients de service, membres, invitations ou connexions d’agents.
  • Deux façons de se connecter. Une clé publique enregistrée (private_key_jwt, la méthode recommandée) ou un secret (client_secret_basic). Un client peut en détenir jusqu’à deux de chaque, pour permettre une rotation sans interruption.

Une organisation contient jusqu’à 50 clients de service actifs, tous projets confondus. Les ID client commencent par scl_ et les secrets par sr_cs_.

Créer une clé

Générer une paire de clés Ed25519 sur la machine qui exécute le client. La clé privée ne la quitte jamais; screenRIG ne conserve que la moitié publique.

openssl genpkey -algorithm ed25519 -out screenrig-client.pem
chmod 600 screenrig-client.pem

Les clés EC P-256 et RSA d’au moins 2048 bits fonctionnent aussi.

Créer le client

Dans le tableau de bord. Ouvrez Accès et choisissez Nouveau client de service. Nommez-le, choisissez Lecture seule, Proposer ou Gérer, cochez ses capacités, et collez la clé publique au format JWK, ou choisissez Générer un secret client. Imprimez la clé publique JWK avec Node :

node -e 'const c=require("node:crypto");const k=c.createPublicKey(require("node:fs").readFileSync("screenrig-client.pem"));console.log(JSON.stringify(k.export({format:"jwk"})))'

Avec la CLI. L’agent a besoin de l’accès Gestion pour son identité avec la portée identity, de sa propre autorisation Gestion avec la capacité project sur le projet ciblé, et d’une personne responsable vérifiée qui administre l’organisation de ce projet.

Pour un agent nouvellement inscrit, la personne invitée doit d’abord accepter l’invitation d’inscription. L’accès Gestion au projet ne suffit pas à autoriser les modifications d’identifiants.

--key-file accepte un fichier JWK ou PEM et n’envoie que sa partie publique :

screenrig service-client create --name "Nightly report" --access read \
  --capability reports --key-file screenrig-client.pem

Pour utiliser un secret à la place, ajoutez --secret-file PATH. Le CLI écrit le secret dans ce nouveau fichier avec le mode 0600 ; il est affiché une fois et jamais plus.

screenrig service-client show scl_ID liste les identifiants clés du client (kid) et les identifiants secrets. Une clé sans son propre kid est connue par son empreinte RFC 7638.

Clients téléverseurs

L’agent d’une agence reçoit un client limité à des listes de lecture et des dossiers de médias choisis : ajoutez --uploader --folder-id mfd_FOLDER[,mfd_FOLDER] --playlist-id pl_PLAYLIST[,pl_PLAYLIST] à service-client create. Un client téléverseur détient uniquement les capacités content et playlists et agit en Proposer sauf si --access dit autre chose. Avec son jeton, il téléverse dans ses dossiers (screenrig media upload FILE --folder mfd_FOLDER) et ajoute, remplace et supprime des pages de ses listes de lecture (screenrig playlist page add|replace|remove) ; tout le reste répond outside_upload_scope, dont upload_scope énumère ce qu’il peut utiliser. Le propriétaire du projet modifie plus tard les dossiers et les listes de lecture d’un client ; le changement s’applique au jeton déjà émis. Voir Téléverseurs.

Obtenir un jeton d’accès

Demander au point de terminaison du jeton un jeton avec l’octroi client_credentials. La réponse contient access_token, token_type: Bearer, expires_in (900 secondes) et scope. Il n’y a pas de jeton de rafraîchissement : redemandez quand il expire.

Avec un secret, authentifiez-vous avec HTTP Basic. Un secret dans le corps de la requête est refusé :

curl -s https://api.screenrig.ai/oauth/token \
  -u "$SCREENRIG_CLIENT_ID:$SCREENRIG_CLIENT_SECRET" \
  -d grant_type=client_credentials

Avec une clé, envoyez une assertion client signée au lieu d’un secret :

curl -s https://api.screenrig.ai/oauth/token \
  -d grant_type=client_credentials \
  -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
  -d client_assertion="$ASSERTION"

L’assertion est un JWT signé avec votre clé privée :

ChampValeur
En-tête algEdDSA, ES256 ou RS256, correspondant à la clé
En-tête kidL’identifiant de la clé enregistrée
iss et subL’identifiant client
audhttps://api.screenrig.ai ou https://api.screenrig.ai/oauth/token, en une seule chaîne
iat, expAu maximum 5 minutes d’intervalle
jtiUne nouvelle valeur aléatoire pour chaque assertion ; une répétition est refusée

Les en-têtes jku, x5u, jwk et x5c sont refusés.

Demander moins

Deux paramètres optionnels restreignent un jeton :

  • scope : access:read ou access:propose donne au jeton un niveau inférieur à celui du client, et les noms de capacités ne conservent que ces capacités, par exemple scope=access:read reports. Un scope au-delà de celui du client est invalid_scope.
  • resource : https://api.screenrig.ai/api, par défaut, pour l’API REST, ou https://api.screenrig.ai/mcp pour le serveur distant.

Appeler l’API

Envoyez le jeton en tant que porteur. Il agit sur le projet propre au client :

curl -s https://api.screenrig.ai/api/screens \
  -H "Authorization: Bearer $TOKEN"

Node : un jeton mis en cache avec une clé

Cet assistant signe l’assertion avec la crypto intégrée de Node, garde le jeton en mémoire et en demande un nouveau une minute avant son expiration. Définissez CLIENT_KEY_ID sur la kid de la clé depuis service-client show :

import { createPrivateKey, randomUUID, sign } from "node:crypto";
import { readFileSync } from "node:fs";

const ISSUER = "https://api.screenrig.ai";
const { SCREENRIG_CLIENT_ID: clientId, SCREENRIG_CLIENT_KEY_FILE: keyFile, CLIENT_KEY_ID: kid } = process.env;
const key = createPrivateKey(readFileSync(keyFile));
const part = (value) => Buffer.from(JSON.stringify(value)).toString("base64url");
let cached;

function assertion() {
  const now = Math.floor(Date.now() / 1000);
  const input = `${part({ alg: "EdDSA", typ: "JWT", kid })}.${part({ iss: clientId, sub: clientId, aud: ISSUER, iat: now, exp: now + 60, jti: randomUUID() })}`;
  return `${input}.${sign(null, Buffer.from(input), key).toString("base64url")}`;
}

export async function accessToken() {
  if (cached && cached.expiresAt - 60_000 > Date.now()) return cached.token;
  const response = await fetch(`${ISSUER}/oauth/token`, {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
      client_assertion: assertion(),
    }),
  });
  const body = await response.json();
  if (!response.ok) throw new Error(`screenRIG token request refused: ${body.error}: ${body.error_description ?? ""}`);
  cached = { token: body.access_token, expiresAt: Date.now() + body.expires_in * 1000 };
  return cached.token;
}

Python : un jeton mis en cache avec un secret

import base64, json, os, time, urllib.parse, urllib.request

ISSUER = "https://api.screenrig.ai"
_cached = {"token": None, "expires_at": 0.0}


def access_token() -> str:
    if _cached["token"] and _cached["expires_at"] - 60 > time.time():
        return _cached["token"]
    pair = f'{os.environ["SCREENRIG_CLIENT_ID"]}:{os.environ["SCREENRIG_CLIENT_SECRET"]}'
    request = urllib.request.Request(
        f"{ISSUER}/oauth/token",
        data=urllib.parse.urlencode({"grant_type": "client_credentials"}).encode(),
        headers={"Authorization": "Basic " + base64.b64encode(pair.encode()).decode()},
    )
    with urllib.request.urlopen(request, timeout=10) as response:
        body = json.load(response)
    _cached.update(token=body["access_token"], expires_at=time.time() + body["expires_in"])
    return body["access_token"]

Exécuter le CLI en tant que client de service

Connectez le CLI en tant que ce client une seule fois, dans une configuration qui lui est propre. La configuration enregistre le client et le fichier qui contient sa clé privée (recommandée) ou son secret, jamais la clé ou le secret eux-mêmes :

screenrig --config /run/screenrig/ci.json login --client-id scl_ID \
  --client-key-file /run/secrets/screenrig-client.pem
screenrig --config /run/screenrig/ci.json screen list

Utilisez --client-secret-file FILE au lieu de --client-key-file pour un secret. Chaque commande qui utilise cette configuration s’exécute en tant que ce client sans étape de connexion : elle lit le fichier de clé ou de secret à chaque fois (un fichier renouvelé n’exige donc aucune nouvelle connexion), garde le jeton en mémoire et n’écrit rien d’autre dans la configuration. screenrig logout retire le client de celle-ci. Chaque commande demande son propre jeton, donc un travail qui exécute plus de quelques centaines de commandes par heure est mieux servi par un jeton réutilisé via l’API.

Faire pivoter et révoquer

ÀTableau de bord (Accès, le client)CLI
Ajouter une seconde cléAjouter une cléscreenrig service-client add-key scl_ID --key-file new.pem
Supprimer une cléSupprimer la cléscreenrig service-client remove-key scl_ID --kid KID
Ajouter un second secretGénérer un secretscreenrig service-client add-secret scl_ID --secret-file new-secret
Supprimer un secretSupprimer le secretscreenrig service-client remove-secret scl_ID --secret-id css_ID
Modifier l’accès ou les capacitésAccès, puis Enregistrer l’accèsUtilisez le tableau de bord
Terminer le clientRévoquerscreenrig service-client revoke scl_ID --yes

Pour faire une rotation sans temps d’arrêt, ajoutez la nouvelle clé ou secret, déployez-le, puis retirez l’ancien. Les jetons émis avec une clé ou un secret retiré cessent de fonctionner en quelques secondes, et un client conserve au moins une clé ou un secret.

Restreindre l’accès ou les capacités d’un client annule ses jetons actuels ; son prochain jeton porte le nouvel accès. La révocation est permanente : les clés et secrets sont supprimés, les jetons du client cessent de fonctionner en quelques secondes, et l’enregistrement reste visible sur la page Accès pour audit. Supprimer le projet révoque ses clients.

Un client obtient des jetons uniquement tant que son projet est actif. Un projet annulé ou supprimé n’en émet aucun. Un projet à crédit nul émet toujours des jetons, et ses requêtes payantes répondent 402 payment_required, comme le fait un agent.

Erreurs et limites

RéponseCauseCorriger
401 invalid_clientSecret incorrect ou supprimé, assertion mauvaise ou répétée, kid inconnu, client révoqué, ou projet annulé ou suppriméVérifiez les identifiants par rapport à service-client show ; envoyez un jti neuf à chaque fois
400 invalid_scopeUn périmètre au-delà de l’accès ou des capacités du clientDemander moins, ou changer de client
400 invalid_targetUn resource autre que les deux listésUtilisez le serveur par défaut ou distant
429 rate_limitedPlus de 600 jetons par client, ou 1 200 requêtes de jetons par adresse, en une heureRéutiliser chaque jeton jusqu’à son expiration
503 temporarily_unavailableRéessayez sous peuRéessayer avec temporisation
401 de /apiLe jeton a expiré ou a été révoquéDemander un nouveau jeton une fois, puis arrêter
403 forbidden lors de la gestion des clientsIl manque l’autorité sur l’identité, une appartenance actuelle au projet avec la capacité project, ou le rôle d’administrateur de l’organisation de la personne responsableFaire accepter l’invitation d’inscription par la personne invitée; confirmer l’accès Gestion à l’identité, l’autorisation Gestion de l’agent sur le projet et le rôle d’administrateur de l’organisation de la personne responsable
403 insufficient_access de /apiLe niveau d’accès du jeton ne peut pas effectuer ce changement, ou le projet exige une révision pour les modifications d’agentUtilisez un client Manage ; pendant que le projet est en révision, les modifications de playlist et d’écran vont dans Révisions
402 payment_required de /apiLe projet n’a pas de crédit pour une demande payanteAjouter du crédit au projet