Ce référentiel propose un serveur de connexion à utiliser avec l'outil de mappage COCODA. Il permet aux utilisateurs de s'authentifier à l'aide de différents fournisseurs (par exemple GitHub, Orcid). Voir https://coli-conc.gbv.de/login/API pour un exemple sur la façon dont vous pourriez l'utiliser.
.envproviders.jsonapplications.jsonLogin-Server nécessite Node.js (> = V18, V20 recommandé) et l'accès à une base de données MongoDB (> = V5, V7 recommandé).
git clone https://github.com/gbv/login-server.git
cd login-server
npm install
# after setting up or changing providers, create indexes
npm run indexesLogin-Server est également disponible via Docker. Veuillez vous référer à la documentation sur https://github.com/gbv/login-server/blob/master/docker/readme.md pour plus de détails.
Si l'exécution du serveur derrière un proxy inversé, assurez-vous d'inclure l'en-tête X-Forwarded-Proto , autorisez toutes les méthodes HTTP et activez WebSocket.
Vous devez fournir deux fichiers de configuration:
.envPour configurer l'application:
# recommended, port for express, default: 3004
PORT=
# recommended, full base URL, default: http://localhost[:PORT]/
# (required when used in production or behind a reverse proxy)
BASE_URL=
# title of application (will be shown in header)
TITLE=My Login Server
# list of allowed origins separated by comma, includes the hostname of BASE_URL by default
ALLOWED_ORIGINS=
# required for some strategies to enable production mode, default: development
NODE_ENV=production
# strongly recommended, imprint and privacy URLs for footer and clients
IMPRINT_URL=
PRIVACY_URL=
# recommended, secret used by the session
SESSION_SECRET=
# optional, maximum number of days a session is valid (rolling), default: 30
COOKIE_MAX_DAYS=
# threshold in minutes when to send "sessionAboutToExpire" events, default: 60
SESSION_EXPIRATION_MESSAGE_THRESHOLD=
# interval in minutes in which to check for expiring sessions, default: 5
SESSION_EXPIRATION_MESSAGE_INTERVAL=
# username used for MongoDB, default: <empty>
MONGO_USER=
# password used for MongoDB, default: <empty>
MONGO_PASS=
# host used for MongoDB, default: 127.0.0.1
MONGO_HOST=
# port used for MongoDB, default: 27017
MONGO_PORT=
# database used for MongoDB, default: login-server
MONGO_DB=
# the rate limit window in ms, default: 60 * 1000
RATE_LIMIT_WINDOW=
# the rate limit tries, default: 10
RATE_LIMIT_MAX=
# a jsonwebtoken compatible keypair
JWT_PRIVATE_KEY_PATH=
JWT_PUBLIC_KEY_PATH=
# the jsonwebtoken algorithm used
JWT_ALGORITHM=
# expiration time of JWTs in seconds, default: 120, min: 10
JWT_EXPIRES_IN=
# URL for Sources, default: https://github.com/gbv/login-server
SOURCES_URL=
# the path to the providers.json file, default: ./providers.json
PROVIDERS_PATH=
# log = log all messages, warn = only log warnings and errors, error = only log errorsl default: log
VERBOSITY=providers.jsonPour configurer les fournisseurs. Voir les fournisseurs.
applications.json Pour fournir à l'utilisateur des informations sur les applications accéder à leurs données et les applications ont lancé une connexion d'une session, vous pouvez fournir une liste d'applications dans applications.json . La liste doit être un tableau d'objets et chaque objet doit avoir une url et name . Exemple:
[
{
"url" : " https://bartoc.org " ,
"name" : " BARTOC "
},
{
"url" : " https://coli-conc.gbv.de/coli-rich/ " ,
"name" : " coli-rich "
},
{
"url" : " https://coli-conc.gbv.de/cocoda/app/ " ,
"name" : " Cocoda "
},
{
"url" : " https://coli-conc.gbv.de/cocoda/dev/ " ,
"name" : " Cocoda (dev) "
},
{
"url" : " https://coli-conc.gbv.de/cocoda/rvk/ " ,
"name" : " Cocoda (RVK) "
},
{
"url" : " https://coli-conc.gbv.de/cocoda/wikidata/ " ,
"name" : " Cocoda (Wikidata) "
},
{
"url" : " https://coli-conc.gbv.de/cocoda/ " ,
"name" : " Cocoda (other) "
},
{
"url" : " https://coli-conc.gbv.de " ,
"name" : " Other coli-conc application "
}
] L'URL doit être accessible car l'interface s'y connectera. Une session est associée à une application si son URL de référence contient l' url de l'application. Les applications seront vérifiées de haut en bas, vous devez donc la commander de l'URL la plus spécifique à l'URL la moins spécifique (voir l'exemple ci-dessus).
npm run startLe serveur fournit une interface Web, une API HTTP et un WebSocket.
L'interface Web permet aux utilisateurs de créer et de gérer des comptes avec des connexions à plusieurs identités chez les fournisseurs d'identité (voir les fournisseurs). Les fournisseurs sont utilisés pour authentifier les utilisateurs car le serveur de connexion ne stocke aucun mot de passe (connexion unique).
L'API HTTP et WebSocket permettent aux applications client d'interagir avec le serveur de connexion, par exemple pour vérifier si un utilisateur a été connecté et pour savoir quelles identités appartiennent à un utilisateur (voir Connexion-client et connexion de connexion).
Le serveur de connexion peut en outre être utilisé pour authentifier les utilisateurs contre d'autres services afin que les utilisateurs puissent prouver leur identité.
bin de répertoire contient un script d'assistance pour l'administration d'une instance de serveur tel que la liste des comptes d'utilisateurs et la gestion des fournisseurs locaux.
Les tests utilisent le même MongoDB que celui configuré dans .env , juste avec le -test PostFix après le nom de la base de données.
npm test Login-Server utilise Passport (GitHub) comme middleware d'authentification. Passport utilise des «stratégies» dites pour soutenir l'authentification avec différents fournisseurs. Une liste des stratégies disponibles peut être trouvée ici. Les stratégies actuellement soutenues dans le serveur de connexion sont:
Étant donné que les stratégies utilisent différents paramètres dans leurs rappels de vérification, chaque stratégie a son propre fichier de wrapper dans les strategies/ . Pour ajouter une autre stratégie à Login-Server, ajoutez un fichier appelé {name}.js (où {name} est le nom de la stratégie qui est utilisé avec passport.authenticate ) avec la structure suivante (github comme exemple):
/**
* OAuth Stategy for GitHub.
*/
// Import strategy here
import { Strategy } from "passport-github"
// Don't change this part!
export default ( options , provider , callback ) => new Strategy ( options ,
// Strategies have different callback parameters.
// `req` is always the first and the `done` callback is always last.
( req , token , tokenSecret , profile , done ) => {
// Prepare a standardized object for the user profile,
// usually using information from the `profile` parameter
let providerProfile = {
// Required, don't change this!
provider : provider . id ,
// Required: Choose a field that represents a unique user ID for this user
id : profile . id ,
// Optional: Provides a display name (e.g. full name)
name : profile . displayName ,
// Optional: Provides a username
username : profile . username
}
// Call a custom callback. `req`, `providerProfile`, and `done` are required,
// `token` and `tokenSecret` can be null.
callback ( req , token , tokenSecret , providerProfile , done )
} )Vous pouvez consulter les stratégies existantes comme exemples et ajouter la vôtre via une demande de traction.
Après avoir ajouté la stratégie, vous pouvez l'utiliser en ajoutant un fournisseur à providers.json :
[
{
"id" : " github " ,
"strategy" : " github " ,
"name" : " GitHub " ,
"template" : " https://github.com/{username} " ,
"options" : {
"clientID" : " abcdef1234567890 " ,
"clientSecret" : " abcdef1234567890abcdef1234567890 "
},
"image" : " https://upload.wikimedia.org/wikipedia/commons/9/91/Octicons-mark-github.svg " ,
"url" : " https://github.com "
}
]Chaque objet de la liste des fournisseurs peut avoir les propriétés suivantes:
id (requis) - ID unique pour le fournisseur.strategy (requise) - Nom de la stratégie de passeport utilisée par le fournisseur.name (requis) - Nom d'affichage du fournisseur.template (facultatif) - Une chaîne de modèle pour générer un URI (le placeur {field} peut être n'importe quel champ fourni dans l'objet providerProfile , généralement {id} ou {username} ).credentialsNecessary (Facultatif) - Défini sur true si le nom d'utilisateur et les informations d'identification de mot de passe sont nécessaires pour ce fournisseur. Au lieu d'une redirection (pour OAuth), Login-Server affichera un formulaire de connexion qui enverra les informations d'identification à un point de terminaison post.options (principalement requises) - Un objet d'options pour la stratégie, contenant souvent des informations d'identification du client pour le point de terminaison de l'authentification.image (Facultatif) - Une image associée au fournisseur. Sera affiché sur la page de connexion et dans la liste des identités connectées. Vous pouvez fournir des images statiques dans le dossier static/ . La valeur de la propriété serait alors static/myimage.svg . Si le nom de fichier correspond à l' id du fournisseur, l'image sera automatiquement associée.url (Facultatif) - Une URL pour le fournisseur; sera lié sur son image / icône sous /account . Il existe des URL par défaut pour les stratégies github , orcid , mediawiki et stackexchange . Ce qui suit est un exemple providers.json qui montre comment configurer chacun des fournisseurs existants:
[
{
"id" : " github " ,
"strategy" : " github " ,
"name" : " GitHub " ,
"template" : " https://github.com/{username} " ,
"options" : {
"clientID" : " abcdef1234567890 " ,
"clientSecret" : " abcdef1234567890abcdef1234567890 "
}
},
{
"id" : " orcid " ,
"strategy" : " orcid " ,
"name" : " ORCID " ,
"template" : " https://orcid.org/{id} " ,
"options" : {
"clientID" : " APP-abcdef1234567890 " ,
"clientSecret" : " abcdef1-23456-7890ab-cdef12-34567890 "
}
},
{
"id" : " mediawiki " ,
"strategy" : " mediawiki " ,
"name" : " Mediawiki " ,
"template" : " https://www.mediawiki.org/wiki/User:{username} " ,
"options" : {
"consumerKey" : " abcdef1234567890 " ,
"consumerSecret" : " abcdef1234567890abcdef1234567890 "
}
},
{
"id" : " stackexchange " ,
"strategy" : " stackexchange " ,
"name" : " Stack Exchange " ,
"template" : " https://stackexchange.com/users/{id} " ,
"options" : {
"clientID" : " 12345 " ,
"clientSecret" : " abcdef1234567890(( " ,
"stackAppsKey" : " 1234567890abcdefg(( "
}
},
{
"id" : " my-ldap " ,
"strategy" : " ldapauth " ,
"name" : " My LDAP " ,
"credentialsNecessary" : true ,
"options" : {
"server" : {
"url" : " ldap://ldap.example.com " ,
"bindDN" : " uid=admin,dc=example,dc=com " ,
"bindCredentials" : " abcdef1234567890 " ,
"searchBase" : " dc=example,dc=com " ,
"searchFilter" : " (uid={{username}}) "
}
}
},
{
"id" : " easydb " ,
"name" : " easydb test provider " ,
"strategy" : " easydb " ,
"credentialsNecessary" : true ,
"options" : {
"url" : " https://easydb5-test.example.com/api/v1/ "
}
},
{
"id" : " some-script " ,
"strategy" : " script " ,
"name" : " Some Script " ,
"credentialsNecessary" : true ,
"template" : " https://example.org/some-script/{id} " ,
"options" : {
"script" : " ./bin/example-script "
}
},
{
"id" : " cbs " ,
"strategy" : " cbs " ,
"name" : " CBS " ,
"credentialsNecessary" : true ,
"template" : " cbs:{id} " ,
"options" : {
"url" : " https://example.com/ext/api/colirich/users/info " ,
"apiKey" : " abcdef1234567890 "
}
}
] Pour configurer les fournisseurs locaux, veuillez utiliser le script fourni sous bin/manage-local.js . Il vous permettra de créer / supprimer les fournisseurs locaux et de créer / supprimer des utilisateurs pour les fournisseurs locaux.
Vous pouvez ajuster le chemin d'accès au fichier providers.json avec PROVIDERS_PATH dans .env .
Notes sur l'utilisation du fournisseur MediaWiki:
"baseURL": "https://www.wikidata.org/" .https://coli-conc.gbv.de/login/login/wikidata/return pour notre instance de connexion à connexion).Notes sur l'utilisation du fournisseur de scripts:
lib/script-strategy.js ).options.script ) peut être relatif au dossier racine du serveur de connexion, soit par un chemin absolu (recommandé pour Docker).bin/example-script .chmod +x ).id définie lorsque l'authentification a réussi. Facultativement, name peut être fourni et sera utilisé comme nom d'affichage.Login-Server propose des jetons Web JSON qui peuvent être utilisés pour s'authentifier contre d'autres services (comme JSKOS-Server). JSONWEBToken est utilisé pour signer les jetons.
Par défaut, un nouveau clés RSA est généré lorsque l'application est démarrée (2048 bits, en utilisant Node-RSA). Cette clé générée sera par défaut disponible dans ./private.key et ./public.key . Vous pouvez donner le fichier ./public.key à tout autre service qui doit vérifier les jetons. Alternativement, la clé publique actuellement utilisée est proposée au point de terminaison / à propos.
Vous pouvez également fournir un chemin personnalisé pour les fichiers clés en définissant JWT_PRIVATE_KEY_PATH et JWT_PUBLIC_KEY_PATH dans .env . Si une ou les deux clés ne peuvent pas être trouvées, les clés seront générées. Par défaut, l'algorithme RS256 est utilisé, mais tout autre algorithme de clé publique peut être utilisé en définissant JWT_ALGORITHM .
Par défaut, chaque jeton est valable pendant 120 secondes. Vous pouvez l'ajuster en définissant JWT_EXPIRES_IN dans .env .
Les jetons sont reçus via le point de terminaison / token ou en utilisant le token de la demande WebSocket de type. De plus, un jeton est envoyé via le WebSocket une fois que l'utilisateur est connecté, puis régulièrement avant l'expiration du dernier jeton.
Exemple comment vérifier un jeton:
import jwt from "jsonwebtoken"
// token, e.g. from user request
let token = "..."
// get public key from file or endpoint
let publicKey = "..."
jwt . verify ( token , publicKey , ( error , decoded ) => {
if ( error ) {
// handle error
// ...
} else {
let { user , iat , exp } = decoded
// user is the user object
// iat is the issued timestamp
// exp is the expiration timestamp
// ...
}
} )Alternativement, vous pouvez utiliser Passport-JWT (l'exemple suivra).
Affiche une page de destination avec des informations générales sur le serveur de connexion.
Affiche un site pour gérer son compte d'utilisateur (s'il est déjà authentifié) ou redirige vers /login (s'il n'est pas authentifié).
Affiche un site pour gérer les sessions de l'utilisateur (s'ils sont authentifiés) ou rediriger vers /login (s'il n'est pas authentifié).
Affiche un site pour se connecter (s'il n'est pas authentifié) ou dirige vers /account (s'il est authentifié).
Si le paramètre de requête redirect_uri est donné, le site redirige vers l'URI spécifié après une connexion réussie. (Si le paramètre est donné, mais vide, il utilisera le référent comme URI.)
Affiche une page de connexion pour un fournisseur. Pour les fournisseurs d'Oauth, cette page redirigera vers la page du fournisseur pour connecter votre identité, qui redirige ensuite vers /login/:provider/return . Pour les fournisseurs utilisant des informations d'identification, cela montrera un formulaire de connexion.
Cette page gère également redirect_uri (voir /login ci-dessus).
Post Endpoint pour les fournisseurs utilisant des informations d'identification. En cas de succès, il redirigera vers /account , sinon il redirigera vers /login/:provider .
Débranche un fournisseur de l'utilisateur et redirige vers /account .
Enregistre l'utilisateur de son compte. Notez que la session restera car elle est utilisée pour les WebSockets. Cela permet à l'application d'envoyer des événements à des lignes Web actifs pour la session en cours, même si l'utilisateur s'est déconnecté.
Affiche un site pour supprimer son compte d'utilisateur.
Commet la suppression du compte utilisateur et redirige vers /login .
Le serveur fournit un point de terminaison de redirection OAuth (URI de redirection) pour chaque fournisseur OAuth.
Point de terminaison de rappel pour les demandes OAuth. Enregistrera l'identité connectée à l'utilisateur (ou créera un nouvel utilisateur si nécessaire) et redirigez vers /account .
Avant la programmation directe de l'API HTTP et de l'API WebSocket, jetez un œil à la bibliothèque de navigateur JavaScript de connexion. Il peut être vu en action ici (source pour ce site).
Renvoie un objet avec title des touches (titre de l'instance de connexion de connexion), env (environnement, comme development ou production ), publicKey (généralement une clé publique RSA) et algorithm (l'algorithme JSONWebtoken utilisé). La clé privée correspondante de la clé publique donnée est utilisée lors de la signature de JWT.
Renvoie une liste des fournisseurs disponibles (interrompus d'informations sensibles).
Renvoie l'utilisateur actuellement connecté. Renvoie une erreur 404 lorsqu'aucun utilisateur n'est connecté.
Renvoie un utilisateur spécifique. Actuellement limité à son propre ID utilisateur.
Ajuste un utilisateur spécifique. Ne peut être utilisé que si le même utilisateur est actuellement connecté. Propriétés autorisées à changer: name (tout le reste sera ignoré).
Supprime toutes les sessions pour l'utilisateur actuel, à l'exception de la session actuelle.
Supprime la session avec SessionID :id (doit être une session pour l'utilisateur actuel).
Renvoie un jeton Web JSON dans le format:
{
"token" : " <JWT> " ,
"expiresIn" : 120
}Voir aussi: JWTS.
Le jeton lui-même contiendra une propriété user (qui contient des informations sur l'utilisateur actuellement connecté, ou est nul si l'utilisateur n'est pas connecté) et une propriété sessionID qui est nécessaire pour s'authentifier dans une connexion WebSocket.
L'API WebSocket à URL / envoie des événements sur l'utilisateur ou la session actuel. Les événements sont envoyés sous forme de chaînes en codés JSON qui ressemblent à ceci:
{
"type" : " event name (see below) " ,
"date" : " date (as ISOString) " ,
"data" : {
"user" : {
"uri" : " URI of user " ,
"name" : " name of user " ,
"identities" : {
"xzy" : {
"id" : " ID of user for provider xzy " ,
"uri" : " URI or profile URL of user for provider xzy " ,
"name" : " display name of user for provider xzy (if available) " ,
"username" : " username of user for provider xzy (if available) "
}
}
}
}
}open - envoyé après l'établissement de la connexion WebSocket, utilisez-le au lieu de ws.onopen !loggedIn - envoyé lorsque l'utilisateur s'est connecté (sera envoyé immédiatement après avoir établi le WebSocket si l'utilisateur est déjà connecté)loggedOut - envoyé lorsque l'utilisateur s'est déconnecté (sera envoyé immédiatement après avoir établi le WebSocket si l'utilisateur n'est pas connecté)updated - envoyé lorsque l'utilisateur a été mis à jour (par exemple, une nouvelle identité, etc.)providers - envoyés après l'établissement de la connexion WebSocket (se compose d'une data.providers de propriété. Profideurs avec une liste des fournisseurs disponibles)about - envoyé après la connexion WebSocket a été établie ( data de la propriété auront le même format que dans get / about)token - Envoyé lorsque l'utilisateur s'est connecté puis à intervalles avant l'expiration du jeton précédent ( data de la propriété auront le même format que dans Get / Token)authenticated - envoyé en réponse à un succès lors de la demande d'authentification (voir ci-dessous)pong - envoyé en réponse à une demande de ping de type (peut être utilisé pour déterminer si le WebSocket est devenu périmé)sessionAboutToExpire - Envoyé lorsque la session actuellement associée est sur le point d'expirererror - envoyé en réponse à un message malformé via le WebSocket (se compose d'une propriété data.message avec un message d'erreur)Vous pouvez également envoyer des demandes au WebSocket. Il faut également être des chaînes codées JSON sous la forme suivante:
{
"type" : " name of request "
} Il s'agit d'une demande spéciale qui utilise un JWT acquis de Get / Token pour associer le WebSocket actuel à une session particulière ( token de propriété de l'objet de demande envoyée)
La demande authenticate est parfois nécessaire lorsque le WebSocket est utilisé à partir d'un domaine différent de Login-Server. Dans ce cas, un jeton doit être demandé via l'API (par exemple en utilisant Fetch avec credentials: "include" ou axios avec l'option withCredentials: true ) et être envoyée via le WebSocket. Le jeton comprend le SessionID crypté qui sera ensuite associé à la connexion WebSocket. Voici un exemple sur la façon dont un flux de travail à partir d'une application Web pourrait ressembler à: https://coli--conc.gbv.de/login/api
Ce qui suit est un exemple simple sur la façon de se connecter à WebSocket.
// Assumes server is run on localhost:3005
let socket = new WebSocket ( "ws://localhost:3005" )
socket . addEventListener ( "message" , ( message ) => {
try {
let event = JSON . parse ( message )
alert ( event . event , event . user && event . user . uri )
} catch ( error ) {
console . warn ( "Error parsing WebSocket message" , message )
}
} ) PRS accepté.
dev comme base. Les modifications de dev seront fusionnées en master uniquement pour les nouvelles versions.Pour les mainteneurs uniquement
Veuillez travailler sur la branche dev pendant le développement (ou mieux encore, développer dans une branche de fonctionnalité et fusionner en dev une fois prêt).
Lorsqu'une nouvelle version est prête (c'est-à-dire que les fonctionnalités sont terminées, fusionnées dans dev et que tous les tests réussissent), exécutez le script de version inclus (remplacez "Patch" par "mineur" ou "major" si nécessaire):
npm run release:patchCe sera:
devdev est à journpm version patch (ou "mineur" / "major")devmasterdevmaster avec des balisesdevAprès avoir exécuté cela, les actions GitHub créeront automatiquement un nouveau projet de version GitHub. Veuillez modifier et publier la version manuellement.
MIT © 2019 Verbundzentral des GBV (VZG)