Nesta parte da série, implementaremos o gerenciamento de sessões e os pontos de extremidade autenticados (pontos de extremidade que exigem que você esteja conectado). Vamos começar!

Se você ainda não leu Parte 1 desta série, sugiro que o faça, pois ele define o layout básico do projeto, bem como o gerenciamento básico de usuários, e é um pré-requisito para a Parte 2!

Gerenciamento de sessões - O modelo

A primeira etapa para a criação de nossos pontos de extremidade de gerenciamento de sessão é a configuração de um modelo a ser usado a partir desses pontos de extremidade para manipular o banco de dados. Ao contrário do modelo de conta, esse modelo não precisa de documentos referenciais nem de nenhuma lógica complicada e, portanto, é bastante simples. A primeira etapa é importar os vários módulos de que precisaremos, bem como obter uma referência à nossa conexão de banco de dados do nosso módulo de banco de dados.

var db = exigir('./../database').mainBucket;
var couchbase = exigir('couchbase');
var uuid = exigir('uuid');

Em seguida, mais uma vez de forma semelhante ao nosso modelo de conta, criamos uma pequena função para remover as propriedades do nosso banco de dados. 'type' é a única que precisamos remover nesse caso também.

função cleanSessionObj(obj) {
excluir obj.tipo;
retorno obj;
}

Agora vamos começar a trabalhar na própria classe do modelo de sessão. Primeiro vem nosso construtor em branco. Gostaria de mencionar neste momento que nossas classes de modelo são totalmente estáticas neste exemplo, mas uma boa prática a ser seguida é retornar suas classes de modelo das operações CRUD estáticas, mas ainda não chegamos ao ponto em que isso seria útil.

função SessionModel() {
}
módulo.exportações = SessionModel;

Agora, para nossa primeira função de modelo de sessão que realmente fará algum trabalho.

SessionModel.criar = função(uid, retorno de chamada) {
};

Dentro dessa função, precisamos criar nosso documento que será inserido e sua chave associada (armazenamos apenas o uid e, em seguida, acessamos as informações restantes dos usuários diretamente do documento da conta por meio do nosso modelo de conta, que você verá mais adiante.

var sessDoc = {
tipo: 'sessão',
lado: uuid.v4(),
uid: uid
};
var sessDocName = 'sess-' + sessDoc.lado;

Em seguida, armazenamos nosso documento de sessão recém-criado em nosso cluster. Você também notará que chamamos nossa função de sanitização acima aqui, bem como definimos um valor de expiração de 60 minutos. Isso fará com que o cluster "expire" a sessão, removendo-a após esse período de tempo.

db.adicionar(sessDocName, sessDoc, {expiração: 3600}, função(erro, resultado) {
retorno de chamada(erro, cleanSessionObj(sessDoc), resultado.cas);
});

Em seguida, precisamos criar uma função em nosso modelo que nos permita recuperar as informações da sessão que armazenamos anteriormente. Para fazer isso, geramos uma chave que corresponde à que teríamos armazenado e, em seguida, executamos uma solicitação get, retornando o uid do usuário da sessão para o nosso retorno de chamada.

SessionModel.obter = função(lado, retorno de chamada) {
var sessDocName = 'sess-' + lado;db.obter(sessDocName, função(erro, resultado) {
se (erro) {
retorno retorno de chamada(erro);
}

retorno de chamada(nulo, resultado.valor.uid);
});
};

E aqui está o sessionmodel.js finalizado:

var db = exigir('./../database').mainBucket;
var couchbase = exigir('couchbase');
var uuid = exigir('uuid');função cleanSessionObj(obj) {
excluir obj.tipo;
retorno obj;
}

função SessionModel() {
}

SessionModel.criar = função(uid, retorno de chamada) {
var sessDoc = {
tipo: 'sessão',
lado: uuid.v4(),
uid: uid
};
var sessDocName = 'sess-' + sessDoc.lado;

db.adicionar(sessDocName, sessDoc, {expiração: 3600}, função(erro, resultado) {
retorno de chamada(erro, cleanSessionObj(sessDoc), resultado.cas);
});
};

SessionModel.obter = função(lado, retorno de chamada) {
var sessDocName = 'sess-' + lado;

db.obter(sessDocName, função(erro, resultado) {
se (erro) {
retorno retorno de chamada(erro);
}

retorno de chamada(nulo, resultado.valor.uid);
});
};

módulo.exportações = SessionModel;

Gerenciamento de sessões - Pesquisa de contas

Antes de começarmos a escrever nosso manipulador de solicitações propriamente dito, precisamos criar um método que nos permita procurar um usuário com base em seu nome de usuário. Não queremos que os usuários tenham que se lembrar de seus IDs! Na parte 1, criamos nosso modelo de conta em accountmodel.js. Vamos voltar a esse arquivo e adicionar um novo método.

AccountModel.getByUsername = função(nome de usuário, retorno de chamada) {
};

O modo como lidamos com esse método é que criaremos uma chave usando o nome de usuário fornecido que pesquisará um dos documentos referenciais que criamos em AccountModel.create. Se não conseguirmos localizar esse documento de referência, presumiremos que o usuário não existe e retornaremos um erro. Se o nome de usuário puder ser localizado e encontrarmos o documento de referência, executaremos um AccountModel.get para localizar o próprio documento do usuário e encaminhar a chamada de retorno para lá. Isso significa que as chamadas para AccountModel.getByUsername retornará o objeto de usuário completo como se você tivesse chamado diretamente AccountModel.get com o uid.

Aqui está a função completa:

AccountModel.getByUsername = função(nome de usuário, retorno de chamada) {
var refdocName = 'nome de usuário-' + nome de usuário;
db.obter(refdocName, função(erro, resultado) {
se (erro && err.código === couchbase.erros.keyNotFound) {
retorno retorno de chamada('Nome de usuário não encontrado');
} mais se (erro) {
retorno retorno de chamada(erro);
}// Extrair o UID que encontramos
var foundUid = resultado.valor.uid;

// Encaminhar para um get normal
AccountModel.obter(foundUid, retorno de chamada);
});
};

Gerenciamento de sessões - Tratamento de solicitações

A última etapa da ativação da criação da sessão é escrever o próprio manipulador de solicitações. Felizmente, a maior parte da lógica importante foi incluída nas seções acima, e nosso manipulador de solicitações simplesmente encaminha as informações para cada uma delas para processamento. Primeiro, validamos nossas entradas do usuário para garantir que tudo o que é necessário foi fornecido. Em seguida, tentamos localizar a conta pelo nome de usuário fornecido. Em seguida, validamos se a senha corresponde ao que o usuário forneceu, fazendo o hash da senha fornecida e comparando-a. E, por fim, criamos a sessão com nosso modelo de sessão recém-criado e retornamos os detalhes ao usuário. Você pode notar que o ID da sessão não é fornecido diretamente ao usuário, mas, em vez disso, é passado no cabeçalho. Isso é para fins de consistência, pois o cabeçalho também é usado para autenticar cada solicitação posteriormente.

aplicativo.postagem('/sessions', função(req, res, próxima) {
se (!req.corpo.nome de usuário) {
retorno res.enviar(400, 'Deve especificar um nome de usuário');
}
se (!req.corpo.senha) {
retorno res.enviar(400, 'Deve especificar uma senha');
}accountModel.getByUsername(req.corpo.nome de usuário, função(erro, usuário) {
se (erro) {
retorno próxima(erro);
}

se (cripta.sha1(req.corpo.senha) !== usuário.senha) {
retorno res.enviar(400, 'As senhas não correspondem');
}

sessionModel.criar(usuário.uid, função(erro, sessão) {
se (erro) {
retorno próxima(erro);
}

res.setHeader('Autorização', 'Portador ' + sessão.lado);

// Excluir a senha por motivos de segurança
excluir usuário.senha;
res.enviar(usuário);
});
});
});

Agora que nossa criação de sessão existe, vamos adicionar um método para autenticar o usuário por solicitação. Por enquanto, coloquei esse método no nosso app.js, mas talvez seja melhor colocá-lo em um arquivo separado mais tarde, quando as rotas começarem a ser separadas em arquivos distintos. Para autenticar o usuário, verificamos o cabeçalho HTTP Authorization padrão, obtemos o ID da sessão e o procuramos usando nosso modelo de sessão. Se tudo correr como planejado, armazenamos o ID de usuário recém-encontrado na solicitação para os manipuladores de rotas posteriores.

função authUser(req, res, próxima) {
req.uid = nulo;
se (req.cabeçalhos.autorização) {
var authInfo = req.cabeçalhos.autorização.dividir(‘ ‘);
se (authInfo[0] === "Portador) {
var lado = authInfo[1];
sessionModel.obter(lado, função(erro, uid) {
se (erro) {
próxima('Seu ID de sessão é inválido');
} mais {
req.uid = uid;
próxima();
}
});
} mais {
próxima('Deve ser autorizado a acessar esse endpoint');
}
} mais {
próxima('Deve ser autorizado a acessar esse endpoint');
}
}

Principalmente com o objetivo de exibir o método authUser em ação, implementei um /me que retorna o documento do usuário. Simplesmente obtemos o modelo da conta com base no uid que foi armazenado na solicitação pelo manipulador authInfo e o retornamos ao cliente, removendo, é claro, a senha primeiro.

aplicativo.obter('/me', authUser, função(req, res, próxima) {
accountModel.obter(req.uid, função(erro, usuário) {
se (erro) {
retorno próxima(erro);
}excluir usuário.senha;
res.enviar(usuário);
});
});

Final

Neste ponto, você já deve ser capaz de criar contas, fazer login nelas e solicitar informações armazenadas sobre o usuário. Aqui está um exemplo usando o usuário que criamos na Parte 1.

> POST /sessions
{
"nome de usuário": "brett19",
"senha": "success!"
}
< 200 OK
Cabeçalho (Autorização): Bearer 0e9dd36c-5e2c-4f0e-9c2c-bffeea72d4f7> GET /me
Cabeçalho (Autorização): Bearer 0e9dd36c-5e2c-4f0e-9c2c-bffeea72d4f7
< 200 OK
{
"uid": “b836d211-425c-47de-9faf-5d0adc078edc”,
"nome": "Brett Lawson",
"nome de usuário": "brett19"
}

A fonte completa desse aplicativo está disponível aqui: https://github.com/brett19/node-gameapi

Aproveite! Brett

Autor

Postado por Brett Lawson, engenheiro de software principal, Couchbase

Brett Lawson é engenheiro de software principal da Couchbase. Brett é responsável pelo projeto e desenvolvimento dos clientes Node.js e PHP do Couchbase, além de desempenhar um papel no projeto e desenvolvimento da biblioteca C, libcouchbase.

10 Comentários

  1. Obrigado por compartilhar isso, a série parece que será interessante por um tempo!

    Pergunta sobre a função SessionModel.get: Ela (ou alguma outra coisa) deve atualizar a expiração no documento da sessão para 3600 nas pesquisas? Caso contrário, quando você fizer login, sua sessão morrerá automaticamente após uma hora, mesmo que você tenha estado ativo durante esse período.

    Estou ansioso pela continuação da série, continue assim.

    1. Ei, Brendon! Você está completamente certo! Dentro da minha função authUser, eu deveria tocar na sessão para renovar o tempo de expiração. Adicionarei isso à próxima parte.

      Abraços, Brett

      1. Você poderia eliminar uma chamada adicional para o banco de dados (já que o Couchbase é compatível com get-and-touch em uma única solicitação) simplesmente adicionando {expiry: 3600} à chamada db.get existente dentro de SessionModel.get

        Ótima série, a propósito, e estou ansioso pelo artigo sobre o Universo.

        1. Ben, a qual artigo do Universe você se refere?

  2. Seillier Xavier outubro 8, 2013 em 6:31 pm

    Obrigado por seus artigos. Tenho uma pergunta sobre \'referential
    documento\'. por que você usou um \'documento referencial\' em vez de uma \'visualização\'

    1. Olá, Seillier, o motivo pelo qual optei por usar um documento referencial nesse caso foi para garantir a consistência do banco de dados. As visualizações têm um atraso na atualização de seus índices, o que significa que, potencialmente, um usuário poderia registrar um nome de usuário específico e, antes de a visualização ser reindexada, outro usuário poderia registrar o mesmo nome de usuário (já que a visualização não retornará esse nome de usuário como sendo usado ainda). Ao usar um documento referencial, podemos garantir a consistência, pois esse atraso não existe.

      Abraços, Brett

  3. A referência a \"finalized accountmodel.js\" deve dizer \"finalized sessionmodel.js\"

    Além disso, talvez você queira incluir uma cópia do snippet para AccountModel.get() também.

    Quanto à implementação da função authUser, eu a coloquei em seu próprio arquivo \'lib/httpauth.js\' da seguinte forma:

    Em seguida, basta passá-lo para a rota:

    1. Obrigado, rdev5! Corrigi o texto como você sugeriu.

  4. Não consigo testar o endpoint no postman com a autorização dos cabeçalhos

    POST /sessions

Deixar uma resposta