Este repositorio ofrece un servidor de inicio de sesión que se utilizará con la herramienta de mapeo de cocoda. Permite a los usuarios autenticarse utilizando diferentes proveedores (por ejemplo, GitHub, Orcid). Consulte https://coli-conc.gbv.de/login/api para un ejemplo sobre cómo podría usar esto.
.envproviders.jsonapplications.jsonEl servidor de inicio de sesión requiere Node.js (> = V18, V20 recomendado) y el acceso a una base de datos MongoDB (> = V5, V7 recomendado).
git clone https://github.com/gbv/login-server.git
cd login-server
npm install
# after setting up or changing providers, create indexes
npm run indexesEl servidor de inicio de sesión también está disponible a través de Docker. Consulte la documentación en https://github.com/gbv/login-server/blob/master/docker/readme.md para obtener más detalles.
Si ejecuta el servidor detrás de un proxy inverso, asegúrese de incluir el encabezado X-Forwarded-Proto , permita todos los métodos HTTP y habilite WebSocket Proxying.
Debe proporcionar dos archivos de configuración:
.envPara configurar la aplicación:
# 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.jsonPara configurar los proveedores. Ver proveedores.
applications.json Para proporcionar al usuario información sobre qué aplicaciones acceden a sus datos y qué aplicación inició el inicio de sesión de una sesión, puede proporcionar una lista de aplicaciones en applications.json . La lista debe ser una variedad de objetos y cada objetos debe tener una url y name . Ejemplo:
[
{
"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 "
}
] La URL debe ser accesible porque la interfaz se vinculará a ella. Una sesión se asocia con una aplicación si su URL de referencia contiene la url de la aplicación. Las aplicaciones se verificarán de arriba a abajo, por lo que debe ordenarla de URL más específica a una URL menos específica (ver ejemplo arriba).
npm run startEl servidor proporciona una interfaz web, una API HTTP y un WebSocket.
La interfaz web permite a los usuarios crear y administrar cuentas con conexiones con múltiples identidades en proveedores de identidad (ver proveedores). Los proveedores se utilizan para autenticar a los usuarios porque el servidor de inicio de sesión no almacena ninguna contraseña (inicio de sesión único).
La API y WebSocket HTTP permiten que las aplicaciones clientes interactúen con el servidor de inicio de sesión, por ejemplo, para verificar si un usuario ha sido iniciado y para averiguar qué identidades pertenecen a un usuario (consulte el cliente inicio de sesión y el registro de sesión de inicio de sesión para un JavaScript Bibliotecas para conectar aplicaciones web con registro de inicio de sesión).
El servidor de inicio de sesión se puede utilizar para autenticar a los usuarios con otros servicios para que los usuarios puedan probar sus identidades.
Directory bin contiene un script auxiliar para la administración de una instancia de servidor, como enumerar cuentas de usuarios y administrar proveedores locales.
Las pruebas usan el mismo MongoDB que se configura en .env , solo con la -test postfix después del nombre de la base de datos.
npm test Login-Server usa pasaporte (GitHub) como middleware de autenticación. El pasaporte utiliza las llamadas "estrategias" para apoyar la autenticación con diferentes proveedores. Puede encontrar una lista de estrategias disponibles aquí. Las estrategias actualmente compatibles en el servidor de inicio de sesión son:
Debido a que las estrategias usan diferentes parámetros en sus devoluciones de llamada Verificar, cada estrategia tiene su propio archivo de envoltura en las strategies/ . Para agregar otra estrategia al servidor de inicio de sesión, agregue un archivo llamado {name}.js (donde {name} es el nombre de la estrategia que se usa con passport.authenticate Authenticate) con la siguiente estructura (github como ejemplo):
/**
* 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 )
} )Puede ver las estrategias existentes como ejemplos y agregar las suyas a través de una solicitud de extracción.
Después de haber agregado la estrategia, puede usarla agregando un proveedor a 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 "
}
]Cada objeto en la lista de proveedores puede tener las siguientes propiedades:
id (requerido) - ID único para el proveedor.strategy (requerida) - Nombre de la estrategia de pasaporte utilizada por el proveedor.name (requerido) - Nombre de visualización del proveedor.template (Opcional): una cadena de plantilla para generar un URI (el marcador de posición {field} puede ser cualquier campo proporcionado en el objeto providerProfile , generalmente {id} o {username} ).credentialsNecessary (opcional): establecer en true si las credenciales de nombre de usuario y contraseña son necesarias para este proveedor. En lugar de una redirección (para OAuth), el servidor de inicio de sesión mostrará un formulario de inicio de sesión que enviará las credenciales a un punto final posterior.options (en su mayoría requeridas): un objeto de opciones para la estrategia, que a menudo contiene credenciales del cliente para el punto final de autenticación.image (opcional): una imagen asociada con el proveedor. Se mostrará en la página de inicio de sesión y en la lista de identidades conectadas. Puede proporcionar imágenes estáticas en la carpeta static/ . El valor para la propiedad sería static/myimage.svg . Si el nombre de archivo coincide con la id del proveedor, la imagen se asociará automáticamente.url (opcional): una URL para el proveedor; estará vinculado en su imagen /icono debajo /account . Existen URL predeterminadas para las estrategias github , orcid , mediawiki y stackexchange . El siguiente es un ejemplo providers.json que muestra cómo configurar cada uno de los proveedores existentes:
[
{
"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 "
}
}
] Para configurar los proveedores locales, utilice el script proporcionado en bin/manage-local.js . Le permitirá crear/eliminar proveedores locales y crear/eliminar usuarios para proveedores locales.
Puede ajustar la ruta al archivo providers.json con PROVIDERS_PATH en .env .
Notas sobre el uso del proveedor de MediaWiki:
"baseURL": "https://www.wikidata.org/" .https://coli-conc.gbv.de/login/login/wikidata/return para nuestra instancia de sesión de sesión de inicio de sesión).Notas sobre el uso del proveedor de script:
lib/script-strategy.js ).options.script ) puede ser relativa a la carpeta raíz del servidor de inicio de sesión o una ruta absoluta (recomendada para Docker).bin/example-script .chmod +x ).id que se establece cuando la autenticación fue exitosa. Opcionalmente, se puede proporcionar name y se utilizará como nombre de pantalla.Login-Server ofrece tokens web JSON que pueden usarse para autenticarse con otros servicios (como JSKOS-Server). JSONWebToken se usa para firmar los tokens.
Por defecto, se genera un nuevo KeyPair RSA cuando se inicia la aplicación (2048 bits, usando Node-RSA). Este KeyPair generado, por defecto, estará disponible en ./private.key y ./public.key . Puede dar el archivo ./public.key a cualquier otro servicio que necesite verificar los tokens. Alternativamente, la clave pública utilizada actualmente se ofrece en el punto final.
También puede proporcionar una ruta personalizada para los archivos clave configurando JWT_PRIVATE_KEY_PATH y JWT_PUBLIC_KEY_PATH en .env . Si no se pueden encontrar una o ambas claves, se generarán las teclas. Por defecto, se usa el algoritmo RS256 , pero cualquier otro algoritmo de clave pública se puede usar configurando JWT_ALGORITHM .
Por defecto, cada token es válido por 120 segundos. Puede ajustar esto configurando JWT_EXPIRES_IN en .env .
Los tokens se reciben a través del punto final /token o mediante el uso de la solicitud WebSocket de token de tipo. Además, se envía un token a través del WebSocket después de que el usuario se inicie sesión y luego regularmente antes de que expire el último token.
Ejemplo de cómo verificar un token:
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
// ...
}
} )Alternativamente, puede usar Passport-JWT (seguirá el ejemplo).
Muestra una página de destino con información general sobre el servidor de inicio de sesión.
Muestra un sitio para administrar la cuenta de usuario (si ya ya está autenticado) o redirige a /login (si no se autentica).
Muestra un sitio para administrar las sesiones del usuario (si se autentica) o redirige a /login (si no se autentica).
Muestra un sitio para iniciar sesión (si no autenticado) o dirige a /account (si se autentica).
Si se proporciona el parámetro de consulta redirect_uri , el sitio redirigirá al URI especificado después de un inicio de sesión exitoso. (Si se da el parámetro, pero vacío, usará el referente como un URI).
Muestra una página de inicio de sesión para un proveedor. Para los proveedores de OAuth, esta página redirigirá a la página del proveedor para conectar su identidad, que luego redirige a /login/:provider/return . Para los proveedores que usan credenciales, esto mostrará un formulario de inicio de sesión.
Esta página también maneja redirect_uri (ver /login arriba).
Post Punto final para proveedores que usan credenciales. Si tiene éxito, redirigirá a /account , de lo contrario, redirigirá a /login/:provider .
Desconecta a un proveedor del usuario y redirige a /account .
Inicie al usuario fuera de su cuenta. Tenga en cuenta que la sesión permanecerá porque se usa para WebSockets. Esto permite que la aplicación envíe eventos a WebSockets activos para la sesión actual, incluso si el usuario ha desconectado.
Muestra un sitio para eliminar la cuenta de usuario.
Comprometa la eliminación de la cuenta de usuario y redirige a /login .
El servidor proporciona un punto final de redirección OAuth (URI de redirección) para cada proveedor de OAuth.
Punto final de devolución de llamada para solicitudes OAuth. Guardará la identidad conectada al usuario (o creará un nuevo usuario si es necesario) y redirigir a /account .
Antes de programar directamente contra la API HTTP y la API de WebSocket, eche un vistazo a la biblioteca del navegador JavaScript de login-cliente. Se puede ver en acción aquí (fuente para ese sitio).
Devuelve un objeto con title de Keys (título de la instancia de servidor de inicio de sesión), env (entorno, como development o production ), publicKey (generalmente una clave pública RSA) y algorithm (el algoritmo JSONWebToken utilizado). La clave privada correspondiente a la clave pública dada se usa al firmar JWTS.
Devuelve una lista de proveedores disponibles (eliminados de información confidencial).
Devuelve el usuario registrado actualmente. Devuelve un error 404 cuando no se registra ningún usuario.
Devuelve un usuario específico. Actualmente restringido a la propia ID de usuario.
Ajusta a un usuario específico. Solo se puede usar si el mismo usuario está iniciado actualmente. Propiedades permitidas para cambiar: name (todo lo demás será ignorado).
Elimina todas las sesiones para el usuario actual, excepto la sesión actual.
Elimina la sesión con SessionID :id (debe ser una sesión para el usuario actual).
Devuelve un token web JSON en el formato:
{
"token" : " <JWT> " ,
"expiresIn" : 120
}Ver también: JWTS.
El token en sí contendrá una propiedad user (que contiene información sobre el usuario iniciado actualmente, o es nulo si el usuario no está conectado) y una propiedad sessionID que se necesita para autenticarse dentro de una conexión WebSocket.
La API de WebSocket en la URL base / envía eventos sobre el usuario o sesión actual. Los eventos se envían como cuerdas codificadas por JSON que se ven así:
{
"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 : se estableció después de que se estableció la conexión WebSocket, use esto en lugar de ws.onopen !loggedIn : enviado cuando el usuario haya iniciado sesión (se enviará inmediatamente después de establecer el WebSocket si el usuario ya está conectado)loggedOut : enviado cuando el usuario ha desconectado (se enviará inmediatamente después de establecer el WebSocket si el usuario no ha iniciado sesión)updated : enviado cuando se actualizó el usuario (por ejemplo, agregó una nueva identidad, etc.)providers : se envió después de WebSocket Connection (consiste en una propiedad data.providers Providers con una lista de proveedores disponibles)about : se envió después de la conexión WebSocket ( data de la propiedad tendrán el mismo formato que en Get /About)token : enviado cuando el usuario ha iniciado sesión y luego en intervalos antes de que expire el token anterior ( data de la propiedad tendrán el mismo formato que en Get /Token)authenticated : enviado como una respuesta de éxito al solicitar la autenticación (ver más abajo)pong : enviado como respuesta a una solicitud de tipo ping (se puede usar para determinar si el WebSocket se ha vuelto obsoleto)sessionAboutToExpire : enviado cuando la sesión actualmente asociada está a punto de caducarerror : enviado como respuesta a un mensaje malformado a través del WebSocket (consiste en una propiedad data.message con un mensaje de error)También puede enviar solicitudes al WebSocket. Estas también tienen que ser cadenas codificadas por JSON en la siguiente forma:
{
"type" : " name of request "
} Esta es una solicitud especial que utiliza un JWT adquirido de GET /Token para asociar el WebSocket actual con una sesión en particular (Solicitud de solicitud del objeto token de propiedades de Solicitud de Objeto)
La solicitud authenticate a veces es necesaria cuando el WebSocket se usa desde un dominio diferente al servidor de inicio de sesión. En ese caso, se debe solicitar un token a través de la API (por ejemplo, usar la búsqueda con credentials: "include" o axios con opciones withCredentials: true ) y enviarse a través del WebSocket. El token incluye el INCRIPCIÓN SessionID que luego se asociará con la conexión WebSocket. Aquí hay un ejemplo sobre cómo podría verse un flujo de trabajo de una aplicación web: https://coli-conc.gbv.de/login/api
El siguiente es un ejemplo simple sobre cómo conectarse al 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 aceptado.
dev como base. Los cambios de dev se fusionarán en master solo para nuevos lanzamientos.Solo para los mantenedores
Trabaje en la rama dev durante el desarrollo (o mejor aún, desarrolle en una rama de características y fusione en dev cuando esté listo).
Cuando una nueva versión está lista (es decir, las características están terminadas, se fusionan en dev y todas las pruebas tienen éxito), ejecute el script de lanzamiento incluido (reemplace "parche" con "menor" o "mayor" si es necesario):
npm run release:patchEsto lo hará:
devdev esté actualizadonpm version patch (o "menor"/"mayor")devmasterdevmaster con etiquetasdevDespués de ejecutar esto, las acciones de GitHub crearán automáticamente un nuevo borrador de lanzamiento de GitHub. Edite y publique el lanzamiento manualmente.
MIT © 2019 Verbundzentrale des GBV (VZG)