Este repositório oferece um servidor de login a ser usado com a ferramenta de mapeamento Cocoda. Ele permite que os usuários se autentiquem usando diferentes fornecedores (por exemplo, Github, Orcid). Consulte https://coli-conc.gbv.de/login/api para um exemplo de como você pode usar isso.
.envproviders.jsonapplications.jsonO Login-Server requer Node.js (> = V18, V20 recomendado) e acesso a um banco de dados 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 indexesO Login-Server também está disponível via Docker. Consulte a documentação em https://github.com/gbv/login-stever/blob/master/docker/readme.md para obter mais detalhes.
Se executar o servidor por trás de um proxy reverso, inclua o cabeçalho X-Forwarded-Proto , permita todos os métodos HTTP e ative o WebSocket proxying.
Você precisa fornecer dois arquivos de configuração:
.envPara configurar o aplicativo:
# 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 os provedores. Ver provedores.
applications.json Para fornecer ao usuário informações sobre quais aplicativos estão acessando seus dados e qual aplicativo iniciou o login de uma sessão, você pode fornecer uma lista de aplicativos em applications.json . A lista deve ser uma variedade de objetos e cada objeto precisa ter um url e name . Exemplo:
[
{
"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 "
}
] O URL deve estar acessível porque a interface será vinculada a ele. Uma sessão está associada a um aplicativo se o URL do referenciador contiver o url do aplicativo. Os aplicativos serão verificados de cima para baixo; portanto, você deve encomendá -lo da URL mais específica até a URL menos específica (veja o exemplo acima).
npm run startO servidor fornece uma interface da Web, uma API HTTP e uma WebSocket.
A interface da Web permite que os usuários criem e gerenciem contas com conexões com várias identidades em provedores de identidade (consulte os provedores). Os provedores são usados para autenticar usuários porque o servidor de login não armazena nenhuma senhas (assinatura única).
A API HTTP e o WebSocket permitem que os aplicativos do cliente interajam com o servidor de login, por exemplo, para verificar se um usuário foi conectado e descobrir quais identidades pertencem a um usuário (consulte o Login-Client e o Login-Client-Vue para obter bibliotecas JavaScript para conectar aplicativos da Web com o Login-Server).
O servidor de login pode ser usado ainda para autenticar os usuários contra outros serviços para que os usuários possam provar suas identidades.
O bin Directory contém script auxiliar para a administração de uma instância do servidor, como listar contas de usuário e gerenciar fornecedores locais.
Os testes usam o mesmo mongodb que configurado em .env , apenas com o -test pós -fix após o nome do banco de dados.
npm test O Login-Server usa o Passport (GitHub) como middleware de autenticação. O passaporte utiliza as chamadas "estratégias" para apoiar a autenticação com diferentes fornecedores. Uma lista de estratégias disponíveis pode ser encontrada aqui. As estratégias atualmente suportadas no Login-Server são:
Como as estratégias usam parâmetros diferentes em seus retornos de chamada de verificar, cada estratégia possui seu próprio arquivo de wrapper nas strategies/ . Para adicionar outra estratégia ao Login-Server, adicione um arquivo chamado {name}.js (onde {name} é o nome da estratégia usada com passport.authenticate ) com a seguinte estrutura (github como exemplo):
/**
* 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 )
} )Você pode ver as estratégias existentes como exemplos e adicionar o seu próprio por meio de uma solicitação de tração.
Depois de adicionar a estratégia, você pode usá -la adicionando um provedor 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 na lista de provedores pode ter as seguintes propriedades:
id (necessário) - ID exclusivo para o provedor.strategy (necessária) - Nome da estratégia de passaporte usada pelo provedor.name (requerido) - Exibir o nome do provedor.template (Opcional) - Uma string de modelo para gerar um URI (o espaço reservado {field} pode ser qualquer campo fornecido no objeto providerProfile , geralmente {id} ou {username} ).credentialsNecessary (OPCIONAL) - Defina como true se as credenciais de nome de usuário e senha forem necessárias para este provedor. Em vez de um redirecionamento (para OAuth), o Login-Server mostrará um formulário de login que enviará as credenciais para um terminal de postagem.options (principalmente necessárias) - Um objeto de opções para a estratégia, geralmente contendo credenciais do cliente para o endpoint de autenticação.image (Opcional) - Uma imagem associada ao provedor. Será mostrado na página de login e na lista de identidades conectadas. Você pode fornecer imagens estáticas na pasta static/ . O valor da propriedade seria static/myimage.svg . Se o nome do arquivo corresponder ao id do provedor, a imagem será automaticamente associada.url (opcional) - um URL para o provedor; será vinculado em sua imagem /ícone em /account . Existem URLs padrão para as estratégias github , orcid , mediawiki e stackexchange . A seguir, é apresentado um exemplo providers.json que mostra como configurar cada um dos provedores 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 os provedores locais, use o script fornecido em bin/manage-local.js . Ele permitirá que você crie/exclua fornecedores locais e crie/exclua usuários para fornecedores locais.
Você pode ajustar o caminho para o arquivo providers.json com PROVIDERS_PATH em .env .
Notas sobre o uso do provedor de mediawiki:
"baseURL": "https://www.wikidata.org/" .https://coli-conc.gbv.de/login/login/wikidata/return para nossa instância de login-sever).Notas sobre o uso do provedor de script:
lib/script-strategy.js ).options.script ) pode ser relativo à pasta raiz do Login Server ou a um caminho absoluto (recomendado para o Docker).bin/example-script .chmod +x ).id definido quando a autenticação foi bem -sucedida. Opcionalmente, name pode ser fornecido e será usado como nome de exibição.O Login-Server oferece tokens da Web JSON que podem ser usados para se autenticar contra outros serviços (como o JSKOS-Server). O JSONWEBTOKEN é usado para assinar os tokens.
Por padrão, um novo teclado RSA é gerado quando o aplicativo é iniciado pela primeira vez (2048 bits, usando o Node-RSA). Isso gerou o KeyPair, por padrão, estará disponível em ./private.key e ./public.key . Você pode fornecer o arquivo ./public.key a qualquer outro serviço que precise verificar os tokens. Como alternativa, a chave pública atualmente usada é oferecida no /sobre o terminal.
Você também pode fornecer um caminho personalizado para os arquivos de chave, definindo JWT_PRIVATE_KEY_PATH e JWT_PUBLIC_KEY_PATH em .env . Se uma ou ambas as chaves não puderem ser encontradas, as chaves serão geradas. Por padrão, o algoritmo RS256 é usado, mas qualquer outro algoritmo de chave pública pode ser usada definindo JWT_ALGORITHM .
Por padrão, cada token é válido por 120 segundos. Você pode ajustar isso definindo JWT_EXPIRES_IN em .env .
Os tokens são recebidos através do terminal /token ou usando a solicitação WebSocket de Type token . Além disso, um token é enviado através do WebSocket depois que o usuário está conectado e, em seguida, regularmente antes do último token expirar.
Exemplo de como verificar um 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
// ...
}
} )Como alternativa, você pode usar o passaporte-jwt (o exemplo seguirá).
Mostra uma página de destino com informações gerais sobre o servidor de login.
Mostra um site para gerenciar a conta de usuário (se já autenticado) ou redirecionar para /login (se não for autenticado).
Mostra um site para gerenciar as sessões do usuário (se autenticado) ou redirecionar para /login (se não for autenticado).
Mostra um site para fazer login (se não for autenticado) ou direcionar para /account (se autenticado).
Se o parâmetro de consulta redirect_uri for fornecido, o site será redirecionado para o URI especificado após um login bem -sucedido. (Se o parâmetro for fornecido, mas vazio, ele usará o referenciador como URI.)
Mostra uma página de login para um provedor. Para os provedores de OAuth, esta página será redirecionada para a página do provedor para conectar sua identidade, que então redireciona para /login/:provider/return . Para fornecedores que usam credenciais, isso mostrará um formulário de login.
Esta página também lida com redirect_uri (veja /login acima).
Post endpoint para fornecedores usando credenciais. Se for bem -sucedido, ele será redirecionado para /account , caso contrário, será redirecionado para /login/:provider .
Desconecta um provedor do usuário e redireciona para /account .
Registra o usuário fora de sua conta. Observe que a sessão permanecerá porque é usada para os websockets. Isso permite que o aplicativo envie eventos para o WebSockets ativos para a sessão atual, mesmo que o usuário tenha registrado.
Mostra um site para excluir a conta de usuário.
Cometem a exclusão da conta de usuário e redireciona para /login .
O servidor fornece um terminal de redirecionamento do OAuth (URI de redirecionamento) para cada provedor de OAuth.
Ponto de extremidade de retorno de chamada para solicitações de OAuth. Salvará a identidade conectada ao usuário (ou criará um novo usuário, se necessário) e redirecionar para /account .
Antes de programar diretamente a API HTTP e a API da WebSocket, dê uma olhada na biblioteca de navegador JavaScript Login-Client. Pode ser visto em ação aqui (fonte para esse site).
Retorna um title objeto com chaves (título da instância do Login-Server), env (ambiente, como development ou production ), publicKey (geralmente uma chave pública da RSA) e algorithm (o algoritmo JSONWETTOKN usado). A chave privada correspondente para a chave pública fornecida é usada ao assinar os JWTs.
Retorna uma lista de fornecedores disponíveis (despojados de informações confidenciais).
Retorna o usuário atualmente conectado. Retorna um erro 404 quando nenhum usuário estiver conectado.
Retorna um usuário específico. Atualmente restrito ao próprio ID de usuário.
Ajusta um usuário específico. Só pode ser usado se o mesmo usuário estiver conectado atualmente. Propriedades permitidas mudar: name (todo o resto será ignorado).
Remove todas as sessões para o usuário atual, exceto a sessão atual.
Remove a sessão com sessionID :id (precisa ser uma sessão para o usuário atual).
Retorna um token da web json no formato:
{
"token" : " <JWT> " ,
"expiresIn" : 120
}Veja também: JWTS.
O token em si conterá uma propriedade user (que contém informações sobre o usuário atualmente conectado ou é nulo se o usuário não estiver conectado) e uma propriedade sessionID necessária para se autenticar em uma conexão WebSocket.
A API do WebSocket no URL base / envia eventos sobre o usuário ou sessão atual. Os eventos são enviados como cordas codificadas por JSON que se parecem com a seguinte:
{
"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 - Enviado depois que a conexão do WebSocket foi estabelecida, use isso em vez de ws.onopen !loggedIn - Enviado quando o usuário estiver conectado (será enviado imediatamente após o estabelecimento do WebSocket se o usuário já estiver conectado)loggedOut - Enviado quando o usuário estiver logado (será enviado imediatamente após o estabelecimento do WebSocket se o usuário não estiver conectado)updated - enviado quando o usuário foi atualizado (por exemplo, adicionou uma nova identidade, etc.)providers - enviados após a criação da conexão WebSocket (consiste em um data.providers de Propriedade.about - Enviado após a criação da conexão WebSocket ( data da propriedade terão o mesmo formato que no get /about)token - Enviado quando o usuário estiver conectado e depois em intervalos antes do expirar o token anterior ( data da propriedade terão o mesmo formato que no get /token)authenticated - enviado como uma resposta de sucesso ao solicitar autenticação (veja abaixo)pong - enviado como resposta a uma solicitação de ping do tipo (pode ser usado para determinar se o WebSocket ficou obsoleto)sessionAboutToExpire - Enviado quando a sessão atualmente associada está prestes a expirarerror - Enviado como resposta a uma mensagem malformada através do WebSocket (consiste em uma propriedade data.message com uma mensagem de erro)Você também pode enviar solicitações para o WebSocket. Estes também precisam ser cordas codificadas por JSON na seguinte forma:
{
"type" : " name of request "
} Esta é uma solicitação especial que usa um JWT adquirido da get /token para associar o WebSocket atual a uma sessão específica (o objeto de solicitação enviado Token Property token )
Às vezes, a solicitação authenticate é necessária quando o WebSocket é usado a partir de um domínio diferente do servidor de login. Nesse caso, um token precisa ser solicitado através da API (por exemplo, usando as credentials: "include" ou axios com opção withCredentials: true ) e ser enviado através do websocket. O token inclui o SessionID criptografado que será associado à conexão WebSocket. Aqui está um exemplo de como um fluxo de trabalho de um aplicativo da web pode parecer: https://coli-conc.gbv.de/login/api
A seguir, é apresentado um exemplo simples de como se conectar ao 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 aceitos.
dev como base. As mudanças do dev serão fundidas em master apenas para novos lançamentos.Apenas para mantenedores
Por favor, trabalhe na filial dev durante o desenvolvimento (ou melhor ainda, desenvolva -se em uma filial de recursos e se mostre para dev quando estiver pronto).
Quando um novo lançamento estiver pronto (ou seja, os recursos são concluídos, mesclados no dev e todos os testes são bem -sucedidos), execute o script de liberação incluído (substitua "patch" por "menor" ou "major", se necessário):
npm run release:patchIsso irá:
devdev está atualizadonpm version patch (ou "menor"/"major")devmasterdevmaster com tagsdevDepois de executar isso, as ações do GitHub criarão automaticamente um novo rascunho do GitHub. Por favor, edite e publique o lançamento manualmente.
MIT © 2019 verbundzentrale des gbv (vzg)