Cet article J'ai essayé d'implémenter l'API Rails avec TDD par RSpec. part1 Partie 2 de cet article. S'il vous plaît voir de part1 si vous le souhaitez. L'objectif cette fois-ci est de pouvoir gérer la fonction de connexion et la fonction de déconnexion de l'authentification de l'utilisateur à l'aide d'octokit. Cet article est assez long. Il y a de nombreuses parties difficiles à comprendre s'il s'agit d'un code fragmentaire uniquement pour les articles, veuillez donc lire votre propre code de manière appropriée et comprendre le contenu. De plus, si vous avez des expressions difficiles à comprendre, veuillez commenter. Ensuite, j'irai pour la première fois.
Tout d'abord, vous devez enregistrer l'application sur github afin de communiquer en utilisant Api sur Github. https://github.com/settings/apps Accédez à cette page et inscrivez-vous à partir de la nouvelle application Github.
Les éléments d'enregistrement sont les suivants.
Application name: -> Unique et gratuit pour nommer votre application
Homepage URL: -> http://localhost:3000 Enregistrez l'URL pour le développement.
Application description: -> Entrez une explication pour qu'elle soit facile à comprendre
Authorization callback URL: -> http://localhost:3000/oauth/github/callback Définition de l'URL pour la redirection
Appuyez sur Enregistrer une application lorsque vous avez terminé. Ensuite, un affichage comme celui-ci est renvoyé.
Owned by: @user_name
App ID: xxxxx
Client ID: Iv1.xxxxxxxxxxxxxxxxxxxxx
Client secret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Utilisez ce ClientID et ClientSecrete pour vous connecter à l'API gitub. Copiez-le quelque part.
octokit
Ensuite, nous allons introduire une gemme appelée octokit.
officiel https://github.com/octokit/octokit.rb
En utilisant octokit, il semble qu'il soit plus facile de se lier avec github. (Je ne sais pas vraiment ce qui se passe à l'intérieur)
Et comme j'ai déjà ajouté la gemme octokit au début, je continuerai telle quelle.
Déplacez-vous vers le terminal.
$ GITHUB_LOGIN='githubuser_name' GITHUB_PASSWORD='github_password' rails c
Tout d'abord, mettez les deux valeurs dans les variables d'environnement. Il s'agit du nom d'utilisateur et du mot de passe que vous utilisez normalement pour vous connecter à github. Assurez-vous ensuite que la console s'ouvre.
Pour le moment
ENV['GITHUB_LOGIN']
Assurez-vous que le contenu est inclus en appuyant sur.
$ client = Octokit::Client.new(login: ENV['GITHUB_LOGIN'], password: ENV['GITHUB_PASSWORD'])
$ client.user
Connectez-vous ensuite à octokit et assurez-vous que les informations utilisateur sont correctement collectées.
Ceci est juste un exercice. À l'avenir, nous le mettrons en œuvre en utilisant ce mécanisme.
Maintenant, créons un modèle utilisateur.
$ rails g model login name url avatar_url provider
Ajoutez des restrictions au niveau de la base de données aux fichiers de migration.
xxxxxxxxx_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
def change
create_table :users do |t|
t.string :login, null: false
t.string :name
t.string :url
t.string :avatar_url
t.string :provider
t.timestamps
end
end
end
Puisque le fichier a été généré, ajoutez null: false à l'attribut de connexion.
$ rails db:migrate
Ensuite, nous ajouterons des restrictions au niveau du modèle. Je voudrais ajouter une validation, mais j'écrirai d'abord à partir du test.
spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe '#validations' do
it 'should have valid factory' do
user = build :user
expect(user).to be_valid
end
it 'should validate presence of attributes' do
user = build :user, login: nil, provider: nil
expect(user).not_to be_valid
expect(user.errors.messages[:login]).to include("can't be blank")
expect(user.errors.messages[:provider]).to include("can't be blank")
end
it 'should validate uniqueness of login' do
user = create :user
other_user = build :user, login: user.login
expect(other_user).not_to be_valid
other_user.login = 'newlogin'
expect(other_user).to be_valid
end
end
end
Le premier test est un test pour vérifier si le factorybot fonctionne Le second est un test pour vérifier si la connexion et le fournisseur sont inclus. Le troisième est un test pour vérifier si la connexion est unique.
De plus, si le factorybot est en ce moment, le même utilisateur sera ajouté quel que soit le nombre de fois qu'il est créé, donc corrigez cela.
spec/factories/user.rb
FactoryBot.define do
factory :user do
sequence(:login) { |n| "a.levine #{n}" }
name { "Adam Levine" }
url { "http://example.com" }
avatar_url { "http://example.com/avatar" }
provider { "github" }
end
end
Résolvez en utilisant la séquence. Cela rend la connexion utilisateur créée à chaque fois unique.
Maintenant, lancez le test.
$ rspec spec/models/user_spec.rb
Ici, confirmez qu'il n'y a pas de faute de frappe et que l'erreur est normalement émise. Le test pour voir si le premier factorybot fonctionne correctement est réussi.
Nous mettrons en œuvre la validation à partir de maintenant.
models/user.rb
class User < ApplicationRecord
validates :login, presence: true, uniqueness: true
validates :provider, presence: true
end
Exécutez le test pour vous assurer qu'il réussit.
Ensuite, écrivez le code pour interagir avec github.
Répertoire ʻCreate app / lib En dessous, créez ʻapp / lib / user_authenticator.rb
.
app/lib/user_authenticator.rb
class UserAuthenticator
def initialize
end
end
À l'origine, le code de test est d'abord écrit en TDD, mais si vous définissez d'abord la classe, l'erreur correcte sera rejetée, il est donc plus rapide de créer le fichier et de définir la classe en premier.
Ensuite, écrivez le test.
Créez un répertoire et des fichiers lib.
spec/lib/user_authenticator_spec.rb
spec/lib/user_authenticator_spec.rb
require 'rails_helper'
describe UserAuthenticator do
describe '#perform' do
context 'when code is incorrenct' do
it 'should raise an error' do
authenticator = described_class.new('sample_code')
expect{ authenticator.perform }.to raise_error(
UserAuthenticator::AuthenticationError
)
expect(authenticator.user).to be_nil
end
end
end
end
Cette fois, nous utiliserons une méthode d'instance appelée perform pour nous connecter et nous connecter.
Premièrement, lorsque le code est inapproprié. (À propos, le code est un jeton unique émis par github, et cette fois il ne reçoit pas réellement ce code, donc le code utilise juste une chaîne de caractères et comment github se comporte pour ce code. En utilisant une maquette pour la pièce, j'essaie de terminer le test sans que le code ne soit réellement émis. Le code est utilisé pour échanger contre un jeton unique à l'utilisateur github.)
Créez une instance avec described_class.new et exécutez la méthode avec authentication.perform. ʻUserAuthenticator :: AuthenticationError` est défini dans sa propre classe.
Quand j'exécute le test, il est dit qu'il n'y a pas de ".perform". Et il est dit que «.user» ne peut pas être utilisé.
Je vais donc l'écrire.
app/lib/user_authenticator.rb
class UserAuthenticator
class AuthenticationError < StandardError; end
attr_reader :user
def initialize(code)
end
def perform
raise AuthenticationError
end
end
Rendez possible la lecture de l'utilisateur à tout moment avec attr_readerd.
Et la performance est également définie.
Définissez ʻAuthenticationError qui hérite de
StandardError et imbriquez-la dans ʻUserAuthenticator
.
La raison pour laquelle je l'élève dans perform est pour que le test soit réussi pour le moment.
Maintenant, lorsque j'exécute le test, il réussit.
$ rspec spec/lib/user_authenticator_spec.rb
Et ensuite, écrivez un test lorsque le code est correct. Mais avant cela, je l'ai utilisé dans devrait soulever une erreur
authenticator = described_class.new('sample_code') authenticator.perform
Ces deux parties
spec/lib/user_authenticator_spec.rb
describe '#perform' do
let(:authenticator) { described_class.new('sample_code') }
subject { authenticator.perform }
Définissez-le comme ceci et utilisez-le dans le quand le code est correct que je vais écrire.
Le tableau d'ensemble est donc maintenant le suivant.
spec/lib/user_authenticator_spec.rb
describe '#perform' do
let(:authenticator) { described_class.new('sample_code') }
subject { authenticator.perform }
context 'when code is incorrenct' do
it 'should raise an error' do
expect{ subject }.to raise_error(
UserAuthenticator::AuthenticationError
)
expect(authenticator.user).to be_nil
end
end
end
Ensuite, écrivez un test lorsque le code est correct
spec/lib/user_authenticator_spec.rb
context 'when code is correct' do
it 'should save the user when does not exists' do
expect{ subject }.to change{ User.count }.by(1)
end
end
Si l'utilisateur est un utilisateur qui n'existe pas dans la base de données à l'avance, User.count est incrémenté de 1. Il s'agit d'un nouvel enregistrement d'utilisateur.
Maintenant, je lance le test mais bien sûr, il échoue. C'est parce que l'action d'exécution dit "Lever une erreur d'authentification" quoi qu'il arrive. Nous allons donc implémenter la méthode perform.
app/lib/user_authenticator.rb
def perform
client = Octokit::Client.new(
client_id: ENV['GITHUB_CILENT_ID'],
client_secret: ENV['GITHUB_CILENT_SECRET'],
)
res = client.exchange_code_for_token(code)
if res.error.present?
raise AuthenticationError
else
end
end
Ce que nous faisons ici, c'est que github certifie le projet au début de l'article. Placez les deux valeurs que client_id et client_secret ont été affichées lorsque vous avez enregistré ce projet sur github au début de cet article dans cette variable d'environnement. Mais cette fois, la valeur réelle n'est pas utilisée. Pour le moment, je vous l'expliquerai plus tard.
client.exchange_code_for_token(code)
Cette partie reste telle quelle, mais le code est échangé contre un jeton.
Le jeton n'est généré que temporairement par l'API github comme décrit ci-dessus.
Ensuite, si la réponse renvoyée est une erreur, elle peut être récupérée avec res.error, de sorte que l'erreur n'est déclenchée que lorsqu'une erreur est incluse.
Maintenant, exécutez le test une fois.
404 - Error: Not Found
Probablement 404 est craché. En effet, les contenus de GITHUB_CILENT_ID et GITHUB_CILENT_SECRET sont vides. Cependant, comme il s'agit d'un test, nous ne pouvons pas entrer la valeur vraie ici. Dans l'idéal, le test ne devrait être complété que par le test, en éliminant autant que possible l'environnement réseau.
J'utilise donc une simulation pour tester. Une simulation consiste à créer une alternative à la communication github de ce côté et à la compléter dans un test.
spec/lib/user_authenticator_spec.rb
context 'when code is incorrenct' do
before do
allow_any_instance_of(Octokit::Client).to receive(
:exchange_code_for_token).and_return(error)
end
Par conséquent, before est utilisé comme ceci, et une méthode appelée allow_any_instance_of est utilisée.
allow_any_instance_of(Nom de l'instance).to receive(:Nom de la méthode).and_return(Valeur de retour)
Utilisez-le comme ça. Vous pouvez l'utiliser pour spécifier la valeur de retour lorsque la méthode spécifiée de l'instance spécifiée est appelée.
Une erreur est renvoyée lors de l'appel de la méthode exchange_code_for_token à partir d'une instance d'Octokit :: Client.
Définissez l'erreur de la valeur de retour.
spec/lib/user_authenticator_spec.rb
context 'when code is incorrenct' do
let(:error) {
double("Sawyer::Resource", error: "bad_verification_code")
}
double est la méthode pour créer une maquette. Sawyer :: Resource est un nom de classe et l'erreur peut être utilisée comme méthode de cette classe. L'erreur réelle peut être fidèlement reproduite.
Maintenant, lorsque j'exécute le test, le premier réussit, mais l'autre échoue. C'est 404, donc c'est la même chose qu'avant.
Le deuxième test est défini de la même manière que la simulation précédente.
spec/lib/user_authenticator_spec.rb
context 'when code is correct' do
before do
allow_any_instance_of(Octokit::Client).to receive(
:exchange_code_for_token).and_return('validaccesstoken')
end
Mais cette fois, au lieu d'émettre une erreur, il renvoie un jeton d'accès valide. Ce n'est pas vraiment une chaîne significative, mais même cette valeur dans le sens où ce n'est pas une erreur fonctionne comme un jeton suffisamment valide pour les tests.
Exécutez le test.
undefined method `error' for "validaccesstoken":String
Un message apparaît.
c'est
app/lib/user_authenticator.rb
if res.error.present?
Concernant cette partie, une erreur s'est produite car j'essayais de lire l'erreur même s'il n'y avait pas d'erreur dans res. Donc, s'il n'y a pas d'erreur, écrivez pour retourner nil.
app/lib/user_authenticator.rb
if res.try(:error).present?
Maintenant, lancez le test.
expected User.count to have changed by 1, but was changed by 0
On peut dire que c'est un message normal car l'opération de sauvegarde n'a pas encore été écrite. Donc, je vais écrire le processus pour enregistrer les données.
app/lib/user_authenticator.rb
client = Octokit::Client.new(
client_id: ENV['GITHUB_CILENT_ID'],
client_secret: ENV['GITHUB_CILENT_SECRET'],
)
token = client.exchange_code_for_token(code)
if token.try(:error).present?
raise AuthenticationError
else
user_client = Octokit::Client.new(
access_token: token
)
user_data = user_client.user.to_h
slice(:login, :avatar_url, :url, :name)
User.create(user_data.merge(provider: 'github'))
end
Réécrivez comme ça. Créez une instance d'utilisateur github à l'aide du jeton retourné en échange de code.
user_client = Octokit::Client.new(
access_token: token
)
Cette partie de ce qui précède fait la même chose que la création d'une instance à l'aide du login et du mot de passe. Le même résultat est produit indépendamment du fait que le jeton soit utilisé ou que le login et le mot de passe soient utilisés.
//C'est juste un échantillon donc vous n'avez pas à le frapper
$ client = Octokit::Client.new(login: ENV['GITHUB_LOGIN'], password: ENV['GITHUB_PASSWORD'])
$ client.user
Plus tôt dans cet article, j'ai tapé une commande comme celle-ci sur la console, et elle fait exactement la même chose. Vous pouvez obtenir les données de l'utilisateur github en faisant réellement client.user. Cependant, le format est Sawyer :: Resource, qui est très difficile à gérer. Ainsi, une fois converti en hachage avec to_h, le contenu est retiré avec la méthode slice. Et il est enregistré dans la base de données car il utilise la méthode create. Le fournisseur est fusionné car le fournisseur ne figure pas dans les données récupérées, vous devez donc l'ajouter vous-même. Si vous ne le joignez pas, vous serez bloqué dans la validation.
Incidemment, j'ai changé res en token. Il est préférable d'utiliser le nom de la variable comme ce qu'il signifie réellement en termes de logique.
Exécutez ensuite le test.
401 - Bad credentials
Ensuite, un tel message change. 401 semble être une erreur renvoyée lorsque vous ne pouvez pas vous connecter, etc. Cependant, cette fois, il ne s'agit que d'une instance simulée, vous n'avez donc pas besoin de pouvoir vous authentifier.
app/lib/user_authenticator.rb
user_data = user_client.user.to_h.
slice(:login, :avatar_url, :url, :name)
Actuellement, il y a une erreur dans cette partie user_client.user. Ainsi, comment retourner lorsque user_client.user est terminé est reproduit avec un simulacre.
spec/lib/user_authenticator_spec.rb
allow_any_instance_of(Octokit::Client).to receive(
:exchange_code_for_token).and_return('validaccesstoken')
allow_any_instance_of(Octokit::Client).to receive(
:user).and_return(user_data)
end
J'ai ajouté: utilisateur. Ajoutez ensuite la variable user_data.
spec/lib/user_authenticator_spec.rb
context 'when code is correct' do
let(:user_data) do
{
login: 'a.levine 1',
url: 'http://example.com',
avatar_url: 'http://example.com/avatar',
name: 'Adam Levine'
}
end
Maintenant, le test s'exécute et réussit.
Assurez-vous également que les valeurs stockées sont correctes.
spec/lib/user_authenticator_spec.rb
allow_any_instance_of(Octokit::Client).to receive(
:exchange_code_for_token).and_return('validaccesstoken')
allow_any_instance_of(Octokit::Client).to receive(
:user).and_return(user_data)
end
it 'should save the user when does not exists' do
expect{ subject }.to change{ User.count }.by(1)
expect(User.last.name).to eq('Adam Levine')
end
Ajoutez la ligne du bas.
Maintenant, exécutez le test et assurez-vous qu'il réussit.
Cependant, bien qu'un nouvel utilisateur soit créé à chaque fois, je souhaite réutiliser l'utilisateur une fois créé. Évidemment, c'est comme faire un nouvel enregistrement à chaque fois, donc c'est inefficace. J'écrirai donc le code pour qu'il puisse être utilisé.
Tout d'abord, j'écrirai à partir du test.
spec/lib/user_authenticator_spec.rb
it 'should reuse already registerd user' do
user = create :user, user_data
expect{ subject }.not_to change{ User.count }
expect(authenticator.user).to eq(user)
end
Créez un utilisateur une fois et utilisez les mêmes user_data pour faire authentifier.perform. Ensuite, vérifiez si l'utilisateur créé par authentifier.perform et l'utilisateur créé par factorybot sont les mêmes.
Exécutez le test pour vous assurer qu'il échoue. Pour le moment, je ne le réutilise pas encore, mais je le crée à chaque fois. Alors, je vais le décrire pour qu'il puisse être utilisé.
app/lib/user_authenticator.rb
- User.create(user_data.merge(provider: 'github'))
+ @user = if User.exists?(login: user_data[:login])
+ User.find_by(login: user_data[:login])
+ else
+ User.create(user_data.merge(provider: 'github'))
+ end
Réécrivez comme ça. Si le même utilisateur existe, créez une branche qui utilise find_by.
L'exécution du test réussit.
Cependant, à ce stade, la quantité de description de la méthode perform est trop importante et la responsabilité de la méthode perform est ambiguë. Puisque la méthode perform a le sens de ce que l'on appelle l'exécution, il est préférable que ce soit une méthode uniquement pour l'exécution. Alors, écrivez la logique qui génère et arrange la valeur dans une autre méthode.
app/lib/user_authenticator.rb
def perform
- client = Octokit::Client.new(
- client_id: ENV['GITHUB_CILENT_ID'],
- client_secret: ENV['GITHUB_CILENT_SECRET'],
- )
- token = client.exchange_code_for_token(code)
if token.try(:error).present?
raise AuthenticationError
else
- user_client = Octokit::Client.new(
- access_token: token
- )
- user_data = user_client.user.to_h.
- slice(:login, :avatar_url, :url, :name)
- @user = if User.exists?(login: user_data[:login])
- User.find_by(login: user_data[:login])
- else
- User.create(user_data.merge(provider: 'github'))
- end
+ prepare_user
end
Supprimez grossièrement cette partie et déplacez-la vers un autre endroit. L'emplacement à déplacer est défini par la méthode privée. La raison en est qu'il définit une valeur qui n'a pas besoin d'être appelée depuis une classe externe.
app/lib/user_authenticator.rb
private
+ def client
+ @client ||= Octokit::Client.new(
+ client_id: ENV['GITHUB_CILENT_ID'],
+ client_secret: ENV['GITHUB_CILENT_SECRET'],
+ )
+ end
+
+ def token
+ @token ||= client.exchange_code_for_token(code)
+ end
+
+ def user_data
+ @user_data ||= Octokit::Client.new(
+ access_token: token
+ ).user.to_h.slice(:login, :avatar_url, :url, :name)
+ end
+
+ def prepare_user
+ @user = if User.exists?(login: user_data[:login])
+ User.find_by(login: user_data[:login])
+ else
+ User.create(user_data.merge(provider: 'github'))
+ end
+ end
attr_reader :code
end
Écrivez-le comme ça. La structure est telle que la méthode inférieure appelle la méthode supérieure et les responsabilités sont clairement séparées.
Exécutez maintenant le test pour vous assurer qu'il n'échoue pas.
C'est la fin du refactoring.
Suivant.
Ensuite, je vais créer un access_token pour railsapi que je crée maintenant. Le jeton obtenu à l'aide de la méthode exchange_code_for_token n'est qu'un jeton pour accéder à l'API github et obtenir des informations utilisateur.Il ne peut donc pas être utilisé pour authentifier la demande d'API rails que nous faisons.
À partir de maintenant, je vais créer un jeton pour l'authentification de la demande de l'API rails que je crée maintenant. Ce jeton est nécessaire lors de l'exécution d'une action de création ou d'une action de suppression. Au contraire, lors de l'exécution d'une action d'index ou d'une action show, acceptez la demande même s'il n'y a pas de jeton. Mais cela dépend de l'application.
Ensuite, je vais créer le jeton, mais je vais d'abord l'écrire à partir du test.
spec/lib/user_authenticator_spec.rb
it "should create and set user's access token" do
expect{ subject }.to change{ AccessToken.count }.by(1)
expect(authenticator.access_token).to be_present
end
Ajout de ce test à la fin.
Puis, après cela, modifiez la méthode perform.
app/lib/user_authenticator.rb
else
prepare_user
+ @access_token = if user.access_token.present?
+ user.access_token
+ else
+ user.create_access_token
+ end
end
De cette manière, le jeton est défini comme l'attribut de l'instance.
app/lib/user_authenticator.rb
attr_reader :user, :access_token
De plus, permettez d'appeler access_token. Pour le moment, l'explication sera expliquée en détail plus loin.
$ rails g model access_token token user:references
Pour le moment, créez un modèle access_token. Cela crée un modèle access_token avec appartient_to: utilisateur.
Définissez également l'association pour le modèle utilisateur.
app/models/user.rb
class User < ApplicationRecord
validates :login, presence: true, uniqueness: true
validates :provider, presence: true
has_one :access_token, dependent: :destroy #ajouter à
end
db/migrate/xxxxxxxxx_create_access_tokne.rb
class CreateAccessTokens < ActiveRecord::Migration[6.0]
def change
create_table :access_tokens do |t|
t.string :token, null: false
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end
Vérifiez également le fichier de migration, ajoutez nill: false au jeton.
Exécutez rails db: migrate
.
Ensuite, préparez-vous au test du jeton d'accès.
spec/models/access_token_spec.rb
require 'rails_helper'
RSpec.describe AccessToken, type: :model do
describe '#validations' do
it 'should have valid factory' do
end
it 'should validate token' do
end
end
end
Maintenant que tout est prêt, lancez le test.
$ rspec spec/lib/user_authenticator_spec.rb
SQLite3::ConstraintException: NOT NULL constraint failed: access_tokens.token
Ensuite, un tel message est craché. Cette erreur semble se produire si vous avez null: false au niveau de la base de données, mais il est nul.
Ensuite, écrivez la logique pour générer le jeton afin qu'il ne soit pas nul. Faites un test avant cela.
spec/models/access_token_spec.rb
describe '#new' do
it 'should have a token present after initialize' do
expect(AccessToken.new.token).to be_present
end
it 'should generate uniq token' do
user = create :user
expect{ user.create_access_token }.to change{ AccessToken.count }.by(1)
expect(user.build_access_token).to be_valid
end
end
Ajoutez ce code à la fin.
Le premier est de savoir si le jeton est correctement inclus lorsque le AccessToken est renouvelé. Je l'écrirai plus tard, mais je l'écrirai plus tard pour que le jeton soit automatiquement entré lorsqu'il est nouveau.
La seconde est de savoir si le nombre d'AccessToken augmente de 1. N'est-il pas pris dans la validation? Qu'il n'atteigne pas la validation ou non, je crée généralement un modèle, j'utilise la première valeur de la seconde, je construis et je vérifie si cela réussit correctement la validation, mais cette fois un peu Étant donné que le jeton est généré automatiquement lorsque vous créez un nouveau jeton spécial, vous ne pouvez pas le tester. Parce que vous ne pouvez pas spécifier un argument comme AccessToken.new (old_token). Avec AccessToken.new, le jeton est automatique.
Écrivons maintenant la logique pour générer le jeton.
app/models/access_token.rb
class AccessToken < ApplicationRecord
belongs_to :user
after_initialize :generate_token
private
def generate_token
loop do
break if token.present? && !AccessToken.exists?(token: token)
self.token = SecureRandom.hex(10)
end
end
end
La méthode spécifiée par after_inialize est exécutée lors de la création du modèle.
Je le tourne en boucle parce que je veux créer un jeton autant de fois que je le souhaite, à moins que les conditions spécifiées par break ne soient remplies. Générez un jeton à l'aide de la classe SecureRandom. Les valeurs sont créées de manière aléatoire, il n'est donc pas garanti que les mêmes valeurs seront générées exactement. Alors faisons une boucle. La condition de rupture a une valeur en jeton. Et la même valeur n'existe pas dans la base de données. Faites une boucle autant de fois que vous le souhaitez, à moins que ce ne soit le cas. Habituellement, il se brise une fois qu'il tourne.
Exécutez le test.
$ rspec spec/models/access_token_spec.rb
$ rspec spec/lib/user_authenticator_spec.rb
Assurez-vous que ce test réussit.
Au fait, ʻuser.create_access_tokendans user_authenticator.rb Cette méthode n'est pas définie quelque part, elle est automatiquement générée par les rails. Le sens reste le même, mais si vous le remplacez de manière facile à comprendre,
AccessToken.create(user_id: user.id)`
Cela a la même signification que cela.
Maintenant que la logique de génération de jetons est terminée, passons à autre chose.
Ensuite, nous allons implémenter l'image globale de la fonction de connexion. Actuellement, un mécanisme pour générer un jeton a été établi, mais une fonction de connexion utilisant ce jeton n'a pas encore été implémentée. Je vais donc mettre en œuvre ce domaine.
Mais écrivez d'abord à partir du test. Je n'ai pas encore terminé le routage, je vais donc commencer par le test de routage. Il n'y a pas de fichier à décrire, alors créez-le.
spec/routing/access_token_spec.rb
require 'rails_helper'
describe 'access tokens routes' do
it 'should route to access_tokens create action' do
expect(post '/login').to route_to('access_tokens#create')
end
end
L'explication de la description est omise.
Lorsque j'exécute le test, il ne dit pas de correspondance / connexion de route, alors modifiez routes.rb.
config/routes.rb
Rails.application.routes.draw do
+ post 'login', to: 'access_tokens#create'
resources :articles, only: [:index, :show]
end
Essai.
A route matches "/login", but references missing controller: AccessTokensController
On dit qu'il n'y a pas de contrôleur, alors je vais en faire un.
$ rails g controller access_tokens
create app/controllers/access_tokens_controller.rb
invoke rspec
create spec/requests/access_tokens_request_spec.rb
Relancez le test. Le test réussit. Ceci termine l'installation du point de terminaison de connexion.
Maintenant, testons le contrôleur. Créez et décrivez le fichier suivant.
spec/controllers/access_tokens_controller_spec.rb
require 'rails_helper'
RSpec.describe AccessTokensController, type: :controller do
describe '#create' do
context 'when invalid request' do
it 'should return 401 status code' do
post :create
expect(response).to have_http_status(401)
end
end
context 'when success request' do
end
end
end
Je m'attends à ce que 401 soit retourné sans authentification. 401 n'est pas autorisé, mais il est sémantiquement non authentifié, il est donc souvent utilisé comme réponse lorsqu'il n'est pas authentifié.
Bien que ce soit encore nouveau, lorsque le contrôleur rails g est utilisé, des fichiers tels que requests / access_tokens_request_spec.rb sont automatiquement générés. C'est le successeur du test du contrôleur, mais la façon dont il est écrit est légèrement différente de controller_spec, donc cette fois j'ai créé et décrit le fichier moi-même à dessein. À l'origine, il est recommandé d'écrire dans request_spec.
Exécutez le test.
AbstractController::ActionNotFound: The action 'create' could not be found for AccessTokensController
Puisque l'action de création n'est pas définie, écrivez-la.
app/controllers/access_tokens_controller.rb
class AccessTokensController < ApplicationController
def create
end
end
Exécutez le test. J'attends 401, mais 204 est de retour. 204 signifie: no_content.
Donc pour le moment, je vais l'écrire dans le contrôleur pour passer le test.
app/controllers/access_tokens_controller.rb
class AccessTokensController < ApplicationController
def create
render json: {}, status: 401
end
end
Exécutez le test pour vous assurer qu'il réussit.
J'ajouterai plus de tests.
spec/controllers/access_token_controller_spec.rb
context 'when invalid request' do
+ let(:error) do
+ {
+ "status" => "401",
+ "source" => { "pointer" => "/code" },
+ "title" => "Authentication code is invalid",
+ "detail" => "You must privide valid code in order to exchange it for token."
+ }
+ end
it 'should return 401 status code' do
post :create
expect(response).to have_http_status(401)
end
+ it 'should return proper error body' do
+ post :create
+ expect(json['errors']).to include(error)
+ end
end
Attendez-vous à ce qu'une erreur res correcte soit renvoyée dans le cas de 401. La déclaration d'erreur est modifiée et utilisée en la copiant à partir du site suivant. https://jsonapi.org/examples/
Exécutez ensuite le test.
expected: {"detail"=>"You must privide valid code in order to exchange it for token.", "source"=>{"pointer"=>"/code"}, "status"=>"401", "title"=>"Authentication code is invalid"}
got: nil
Puisque nil est renvoyé, écrivez un processus qui renvoie correctement une erreur du côté de la commande.
app/controllers/access_tokens_controller.rb
class AccessTokensController < ApplicationController
def create
error = {
"status" => "401",
"source" => { "pointer" => "/code" },
"title" => "Authentication code is invalid",
"detail" => "You must privide valid code in order to exchange it for token."
}
render json: { "errors": [ error ] }, status: 401
end
end
Assurez-vous que cela passe le test.
Actuellement, lorsque l'action de création est appelée, elle donne une erreur dans tout, mais corrige-la.
app/controllers/access_tokens_controller.rb
class AccessTokensController < ApplicationController
rescue_from UserAuthenticator::AuthenticationError, with: :authentication_error
def create
authenticator = UserAuthenticator.new(params[:code])
authenticator.perform
end
private
def authentication_error
error = {
"status" => "401",
"source" => { "pointer" => "/code" },
"title" => "Authentication code is invalid",
"detail" => "You must privide valid code in order to exchange it for token."
}
end
J'édite également le code pour le refactoring. À ce stade, nous écrivons finalement ʻUserAuthenticator.new (params [: code]) `. La logique pour créer un utilisateur en échangeant du code et des jetons, que j'ai écrits tout le temps, est écrite dans UserAuthenticator, mais je l'appelle ici.
Puis exécutez-le avec perform.
Le corps de l'erreur 401 est écrit dans la méthode. L'erreur renvoyée à ce stade est ʻUserAuthenticator :: AuthenticationError`, donc rescue_from la sauvera. Puisqu'il est écrit dans la méthode, il peut être appelé avec rescue_from.
Après cela, dans UserAuthenticator :: AuthenticationError, je souhaite émettre la même erreur même lorsque le code est vide. Au fait, j'ai besoin de refactoriser.
app/lib/user_authenticator.rb
def perform
raise AuthenticationError if code.blank? || token.try(:error).present?
prepare_user
@access_token = if user.access_token.present?
user.access_token
else
user.create_access_token
end
end
Vous pouvez maintenant obtenir une erreur lorsque le code est vide.
Pour récapituler, le code est le jeton envoyé depuis le frontal. Le frontal récupère le jeton de github et l'envoie à l'api. C'est du code (github_access_code). L'API reçoit le code et communique avec GitHub pour échanger le code contre un jeton (par la méthode exchange_code_for_token). Avec ce jeton, les informations utilisateur github peuvent être obtenues à partir de l'API github.
Sur cette base, il est possible que le code soit suffisamment vide, alors préparez une erreur.
Exécutez le test pour vous assurer qu'il réussit.
Refactor supplémentaire.
app/controlers/access_token_controller.rb
class AccessTokensController < ApplicationController
- rescue_from UserAuthenticator::AuthenticationError, with: :authentication_error
def create
authenticator = UserAuthenticator.new(params[:code])
authenticator.perform
end
- private
-
- def authentication_error
- error = {
- "status" => "401",
- "source" => { "pointer" => "/code" },
- "title" => "Authentication code is invalid",
- "detail" => "You must privide valid code in order to exchange it for token."
- }
- render json: { "errors": [ error ] }, status: 401
- end
end
app/controllers/application_controller.rb
class ApplicationController < ActionController::API
+ rescue_from UserAuthenticator::AuthenticationError, with: :authentication_error
+ private
+ def authentication_error
+ error = {
+ "status" => "401",
+ "source" => { "pointer" => "/code" },
+ "title" => "Authentication code is invalid",
+ "detail" => "You must privide valid code in order to exchange it for token."
+ }
+ render json: { "errors": [ error ] }, status: 401
+ end
end
Laissez complètement authentification_error à l'application_controller afin que tous les contrôleurs puissent détecter cette erreur. La raison en est que des erreurs d'authentification peuvent survenir sur n'importe quel contrôleur.
Exécutez le test pour vous assurer que rien n'a changé.
Et il est encore mieux d'utiliser cette implémentation dans les tests. Le code va coller toutes les modifications pour l'instant car la description sera longue
spec/controllers/access_token_controller_spec.rb
RSpec.describe AccessTokensController, type: :controller do
describe '#create' do
- context 'when invalid request' do
+ shared_examples_for "unauthorized_requests" do
let(:error) do
{
"status" => "401",
@ -11,17 +11,34 @@ RSpec.describe AccessTokensController, type: :controller do
"detail" => "You must privide valid code in order to exchange it for token."
}
end
it 'should return 401 status code' do
- post :create
+ subject
expect(response).to have_http_status(401)
end
it 'should return proper error body' do
- post :create
+ subject
expect(json['errors']).to include(error)
end
end
+ context 'when no code privided' do
+ subject { post :create }
+ it_behaves_like "unauthorized_requests"
+ end
+ context 'when invalid code privided' do
+ let(:github_error) {
+ double("Sawyer::Resource", error: "bad_verification_code")
+ }
+ before do
+ allow_any_instance_of(Octokit::Client).to receive(
+ :exchange_code_for_token).and_return(github_error)
+ end
+ subject { post :create, params: { code: 'invalid_code' } }
+ it_behaves_like "unauthorized_requests"
+ end
context 'when success request' do
end
J'aimerais que vous lisiez attentivement le code pour voir ce que vous faites, mais ici, nous utilisons deux tests avec shared_examples_for.
should return 401 status code
should return proper error body
Ces deux tests seront souvent réutilisés à l'avenir. Vous pouvez également appeler shared_examples_for en utilisant it_behaves_like. En utilisant le sujet et en le réglant sur DRY, vous pouvez entrer librement une valeur pour chaque sujet.
spec/controllers/access_token_controller_spec.rb
let(:github_error) {
double("Sawyer::Resource", error: "bad_verification_code")
}
before do
allow_any_instance_of(Octokit::Client).to receive(
:exchange_code_for_token).and_return(github_error)
end
Aussi, concernant cette partie, cette description a été utilisée dans le test avant, et elle est reproduite avec simulacre sans se connecter directement à l'API github. Cela vous permet de reproduire l'API github sans vous connecter réellement à github.
Ensuite, j'écrirai un test lorsque le code est correct.
spec/controllers/access_token_controller_spec.rb
context 'when success request' do
let(:user_data) do
{
login: 'a.levine 1',
url: 'http://example.com',
avatar_url: 'http://example.com/avatar',
name: 'Adam Levine'
}
end
before do
allow_any_instance_of(Octokit::Client).to receive(
:exchange_code_for_token).and_return('validaccesstoken')
allow_any_instance_of(Octokit::Client).to receive(
:user).and_return(user_data)
end
subject { post :create, params: { code: 'valid_code' } }
it 'should return 201 status code' do
subject
expect(response).to have_http_status(:created)
end
end
Il s'agit simplement d'un simulacre de manipulation du fait que le code est correct ou incorrect. Je m'attends simplement à ce que 201 soit renvoyé si le code est correct.
Exécutez le test.
expected the response to have status code :created (201) but it was :no_content (204)
Ce message s'affiche Donc, éditez le contrôleur de sorte que 201 soit renvoyé en réponse.
app/controlers/access_token_controller.rb
def create
authenticator = UserAuthenticator.new(params[:code])
authenticator.perform
render json: {}, status: :created
end
Ajouter le rendu et le retour créés.
Maintenant, exécutez à nouveau le test et assurez-vous qu'il réussit.
Ensuite, je veux l'implémenter afin qu'il renvoie une réponse fermement. Je vais donc écrire à partir du test.
spec/controllers/access_token_controller_spec.rb
it 'should return proper json body' do
expect{ subject }.to change{ User.count }.by(1)
user = User.find_by(login: 'a.levine 1')
expect(json_data['attributes']).to eq(
{ 'token' => user.access_token.token }
)
end
Ajoutez ce test à la fin. Quant au contenu du test, comme dans le cas de l'article, recevez la valeur avec json_data ['attributes'] et vérifiez si le contenu est correct. Puisque l'utilisateur récupéré par User.find_by est décrit par mock à l'aide de user_data décrit précédemment, le test que la valeur et la valeur renvoyée en tant que réponse sont les mêmes.
Cependant, même si j'exécute le test, je ne peux pas le récupérer avec json_data car je n'utilise pas de sérialiseur et json.data n'existe pas. Donc, nous allons introduire le sérialiseur pour faire une réponse dans un format soigné.
$ rails g serializer access_token
Cela sera décrit dans le fichier créé.
app/serializers/access_token_serializer.rb
class AccessTokenSerializer < ActiveModel::Serializer
attributes :id, :token
end
Ajoutez la description du jeton. Cela permet à la réponse d'inclure un jeton.
Et spécifiez également la valeur à renvoyer par le rendu dans le contrôleur.
access_tokens_controller.rb
- render json: {}, status: :created
+ render json: authenticator.access_token, status: :created
end
Cela vous permet de renvoyer une réponse bien formée au lieu d'un hachage de.
Exécutez le test. Puis un message apparaît.
expected: {"token"=>"6c7c4213cb78c782f6f6"}
got: {"token"=>"2e4c724d374019f3fb26"}
Quelque part, le jeton a été recréé et la valeur a été changée. C'est un bogue qui crée des jetons à chaque fois que vous rechargez.
Donc, je vais écrire un test pour corriger le bogue.
spec/models/access_token_spec.rb
it 'should generate token once' do
user = create :user
access_token = user.create_access_token
expect(access_token.token).to eq(access_token.reload.token)
end
Tout d'abord, lancez un test pour voir si le bogue peut être reproduit.
expected: "3afe2f824789a229014c" got: "c5e04c73aa7ff89fd0a1"
J'ai pu le reproduire correctement, j'ai donc reçu un message.
Améliorons-nous. Jetons d'abord un coup d'œil à la méthode buggy generate_token.
app/models/access_token.rb
def generate_token
loop do
break if token.present? && !AccessToken.exists?(token: token)
self.token = SecureRandom.hex(10)
end
end
Il y a quelque chose qui ne va pas ici, le problème était que la condition de rupture n'était pas bonne.
break if token.present? && !AccessToken.exists?(token: token)
Cette condition a une valeur solide en jeton. Et le jeton n'existe pas dans la base de données. Cela devient une condition. Mais ce serait un peu une contradiction. L'existence du jeton signifie qu'il est stocké dans la base de données, donc cette expression conditionnelle ne peut pas être satisfaite.
Par conséquent, il est condition qu'il n'y ait aucun jeton autre que le jeton spécifié qui a le même jeton.
app/models/access_token.rb
- break if token.present? && !AccessToken.exists?(token: token)
+ break if token.present? && !AccessToken.where.not(id: id).exists?(token: token)
De cette manière, il est possible de créer une condition appelée jeton autre que le jeton actuellement spécifié. Maintenant, exécutez le test et assurez-vous qu'il réussit.
Maintenant, implémentons la fonction de déconnexion.
spec/routeing/access_token_spec.rb
it 'should route to acces_tokens destroy action' do
expect(delete '/logout').to route_to('access_tokens#destroy')
end
Écrivez un test de routage.
config/routes.rb
Rails.application.routes.draw do
post 'login', to: 'access_tokens#create'
delete 'logout', to: 'access_tokens#destroy'
resources :articles, only: [:index, :show]
end
Ajout d'une ligne de déconnexion.
Le test réussit.
Ensuite, j'écrirai un test pour le contrôleur.
spec/controllers/access_token_controller.rb
@@ -1,9 +1,9 @@
require 'rails_helper'
RSpec.describe AccessTokensController, type: :controller do
- describe '#create' do
+ describe 'POST #create' do
shared_examples_for "unauthorized_requests" do
- let(:error) do
+ let(:authentication_error) do
{
"status" => "401",
"source" => { "pointer" => "/code" },
@ -19,7 +19,7 @@ RSpec.describe AccessTokensController, type: :controller do
it 'should return proper error body' do
subject
- expect(json['errors']).to include(error)
+ expect(json['errors']).to include(authentication_error)
end
end
@ -74,4 +74,33 @@ RSpec.describe AccessTokensController, type: :controller do
end
end
end
+ describe 'DELETE #destroy' do
+ context 'when invalid request' do
+ let(:authorization_error) do
+ {
+ "status" => "403",
+ "source" => { "pointer" => "/headers/authorization" },
+ "title" => "Not authorized",
+ "detail" => "You have no right to access this resource."
+ }
+ end
+
+ subject { delete :destroy }
+
+ it 'should return 403 status code' do
+ subject
+ expect(response).to have_http_status(:forbidden)
+ end
+
+ it 'should return proper error json' do
+ subject
+ expect(json['errors']).to include(authorization_error)
+ end
+ end
+
+ context 'when valid request' do
+
+ end
+ end
end
Initialement traité comme une erreur 403, mais renommé pour clarifier le rôle. Ensuite, j'écrirai tout un test dédié à détruire. Continuez à lire le contenu.
La notation de @@ est un code qui indique le nombre de lignes décrites, et il n'est pas nécessaire de l'écrire réellement.
Ensuite, implémentez le contrôleur.
app/controllers/access_tokens_controller.rb
def destroy
raise AuthorizationError
end
Définissez la méthode de destruction. Tout d'abord, pour réussir le test de réponse d'erreur, augmentez AuthorizationError et définissez réellement l'état réel de l'erreur dans application_controller.
app/controllers/application_controller.rb
class ApplicationController < ActionController::API
+ class AuthorizationError < StandardError; end
rescue_from UserAuthenticator::AuthenticationError, with: :authentication_error
+ rescue_from AuthorizationError, with: :authorization_error
private
@ -12,4 +14,14 @@ class ApplicationController < ActionController::API
}
render json: { "errors": [ error ] }, status: 401
end
+ def authorization_error
+ error = {
+ "status" => "403",
+ "source" => { "pointer" => "/headers/authorization" },
+ "title" => "Not authorized",
+ "detail" => "You have no right to access this resource."
+ }
+ render json: { "errors": [ error ] }, status: 403
+ end
end
Le contenu de l'erreur est le même que ce qui a été écrit dans le test.
Maintenant, exécutez le test et assurez-vous qu'il réussit.
Cependant, comme il y a une description légèrement dupliquée, je vais la rendre sèche.
spec/controllers/access_tokens_controller_spec.rb
describe 'DELETE #destroy' do
shared_examples_for 'forbidden_requests' do
end
Tout d'abord, utilisez shared_examples_for sous describe pour résumer la description.
La description suivante est incluse dans shared_examples_for.
spec/controllers/access_tokens_controller_spec.rb
shared_examples_for 'forbidden_requests' do
let(:authorization_error) do
{
"status" => "403",
"source" => { "pointer" => "/headers/authorization" },
"title" => "Not authorized",
"detail" => "You have no right to access this resource."
}
end
it 'should return 403 status code' do
subject
expect(response).to have_http_status(:forbidden)
end
it 'should return proper error json' do
subject
expect(json['errors']).to include(authorization_error)
end
end
Combinez les tests que vous avez écrits jusqu'à présent en un seul.
spec/controllers/access_tokens_controller_spec.rb
context 'when invalid request' do
subject { delete :destroy }
it_behaves_like 'forbidden_requests'
end
Et puisque c'est la description que it_behaves_likes appelle shared_expample_for, il appelle les requêtes interdites spécifiées précédemment dans la chaîne de caractères.
Maintenant que nous avons créé le même environnement qu'auparavant, exécutez-le à nouveau et assurez-vous que le test réussit.
Ensuite, nous allons combiner ces shared_example_for dans un seul fichier afin qu'ils puissent être utilisés. Il y a deux shared_example_for dans le fichier ʻaccess_tokens_controller_spec.rb` actuel, alors mettez-les ensemble dans le même fichier.
Créez spec / support / shared / json_errors.rb
Mettez-y la description de shared_example_for.
spec/support/shared/json_errors.rb
require 'rails_helper'
shared_examples_for 'forbidden_requests' do
let(:authorization_error) do
{
"status" => "403",
"source" => { "pointer" => "/headers/authorization" },
"title" => "Not authorized",
"detail" => "You have no right to access this resource."
}
end
it 'should return 403 status code' do
subject
expect(response).to have_http_status(:forbidden)
end
it 'should return proper error json' do
subject
expect(json['errors']).to include(authorization_error)
end
end
shared_examples_for "unauthorized_requests" do
let(:authentication_error) do
{
"status" => "401",
"source" => { "pointer" => "/code" },
"title" => "Authentication code is invalid",
"detail" => "You must privide valid code in order to exchange it for token."
}
end
it 'should return 401 status code' do
subject
expect(response).to have_http_status(401)
end
it 'should return proper error body' do
subject
expect(json['errors']).to include(authentication_error)
end
end
Ensuite, toute la description de la source de découpe est supprimée.
spec/controllers/access_tokens_controller_spec.rb
describe 'DELETE #destroy' do
subject { delete :destroy }
Augmentez d'une étape l'imbrication de la définition du sujet. Ajoutez ensuite deux tests.
spec/controllers/access_tokens_controller_spec.rb
describe 'DELETE #destroy' do
subject { delete :destroy }
context 'when no authorization header provided' do
it_behaves_like 'forbidden_requests'
end
context 'when invalid authorization header provided' do
before { request.headers['authorization'] = 'Invalid token' }
it_behaves_like 'forbidden_requests'
end
context 'when valid request' do
end
end
Dans ce test, le sujet n'est pas écrit car le sujet est déjà écrit dans shared_example_for, donc subject {delete: destroy}
est automatiquement appelé.
Et si vous utilisez avant, vous pouvez modifier le contenu de la demande.
Cette fois, en mettant Invalid_token dans token, nous allons créer un utilisateur qui n'a pas été authentifié.
Bien sûr, une erreur d'authentification se produira, donc un test qui s'y attend.
Exécutez maintenant le test pour vous assurer qu'il réussit.
spec/controllers/access_tokens_controller_spec.rb
context 'when valid request' do
let(:user) { create :user }
let(:access_token) { user.create_access_token }
before { request.headers['authorization'] = "Bearer #{access_token.token}" }
it 'should return 204 status code' do
subject
expect(response).to have_http_status(:no_content)
end
it 'should remove the proper access token' do
expect{ subject }.to change{ AccessToken.count }.by(-1)
end
end
Ensuite, écrivez un test pour quand la demande est valide.
Pour envoyer la requête correcte, vous devez d'abord mettre le jeton dans headers ['authorisation']
et transmettre les permissions.
Le porteur est l'authentification du porteur, et cette fois nous l'utiliserons.
Les tests s'attendent à ce que le modèle AccessToken soit réduit de un à partir de la base de données.
Assurez-vous maintenant que le test échoue correctement. Ici, une faute de frappe est souvent trouvée si vous confirmez qu'elle échoue correctement.
expected the response to have status code :no_content (204) but it was :forbidden (403)
Lorsque j'exécute le test, je reçois un message comme celui-ci:
Forbidden est renvoyé car il est décrit de sorte que l'action de destruction renvoie toujours une erreur.
Nous allons donc mettre en œuvre l'action de destruction.
#### **`app/controllers/access_tokens_controller.rb`**
```rb
def destroy
raise AuthorizationError
end
Tout d'abord, ce que vous voulez faire avec cette destruction est de détruire le access_token de l'utilisateur qui a envoyé la requête. Alors écrivez comme suit.
app/controllers/access_tokens_controller.rb
def destroy
raise AuthorizationError unless current_user
current_user.access_token.destroy
end
current_user fait référence à l'utilisateur actuellement connecté. Réfléchissez à la façon d'apporter current_user.
current_user ne peut pas être obtenu à partir de la requête à la fois. Cependant, si vous utilisez request.authorization, le Bearer xxxxxxxxxxxxxxxxxxxxx
que vous avez envoyé dans le test plus tôt
Vous pouvez obtenir un jeton comme celui-ci. Utilisez donc ce jeton pour obtenir le current_user.
app/controllers/access_tokens_controller.rb
def destroy
provided_token = request.authorization&.gsub(/\ABearer\s/, '')
access_token = AccessToken.find_by(token: provided_token)
current_user = access_token&.user
raise AuthorizationError unless current_user
current_user.access_token.destroy
end
Tout d'abord, afin d'obtenir le jeton avec request.authorization et de rechercher le jeton dans la base de données, la méthode gsub est utilisée pour le couper avec une expression régulière. Si vous ne pouvez récupérer que la partie numérique du jeton, recherchez avec AccessToken.find_by et récupérez-la. Et si vous utilisez ce access_token.user, vous pouvez récupérer l'utilisateur qui a envoyé la demande. Et détruisez ce jeton Ensuite, la déconnexion est terminée.
La description de & .
est appelée un opérateur bocce, et si vous l'ajoutez à une méthode dont vous savez à l'avance que nil peut revenir et devenir comme une méthode undifind, une erreur se produira dans le cas de nil. N'apparaît pas et nil est renvoyé comme valeur de retour telle quelle, donc aucune erreur ne se produit. quelque chose comme. Cette fois, Invalid_token peut être mélangé dans la requête, donc dans ce cas nil sera retourné, donc une erreur se produira à moins que l'opérateur Bocchi ne soit utilisé.
Exécutez maintenant le test et assurez-vous qu'il réussit tous les tests.
Ensuite, nous refactoriserons ce code.
app/controllers/access_tokens_controller.rb
def destroy
- provided_token = request.authorization&.gsub(/\ABearer\s/, '')
- access_token = AccessToken.find_by(token: provided_token)
- current_user = access_token&.user
-
- raise AuthorizationError unless current_user
current_user.access_token.destroy
end
Tout d'abord, découpez la description comme ceci. Ensuite, déplacez la description vers application_controller.rb. La raison en est que la logique qui reçoit cette requête et génère le current_user est une description que tout contrôleur souhaite utiliser.
app/controllers/application_controller.rb
private
def authorize!
raise AuthorizationError unless current_user
end
def access_token
provided_token = request.authorization&.gsub(/\ABearer\s/, '')
@access_token = AccessToken.find_by(token: provided_token)
end
def current_user
@current_user = access_token&.user
end
Ensuite, écrivez la méthode comme celle-ci en privé. La méthode authorize! Donne une erreur 401 lorsque current_user n'est pas inclus. Utilisez la méthode access_token pour récupérer le bon access_token La méthode current_user récupère l'utilisateur pour ce jeton. La raison pour laquelle access_token et current_user sont séparés ici est de clarifier leurs rôles et leurs responsabilités distinctes.
Et enfin, écrivez pour que la méthode authorize! Définie puisse toujours être appelée.
app/controllers/access_tokens_controller.rb
class AccessTokensController < ApplicationController
before_action :authorize!, only: :destroy
La situation est toujours appelée par before_action. La raison pour laquelle seul destroy est spécifié est que s'il est appelé pendant l'action de création, ce sera une méthode qui ne peut pas être appelée.
Ces approches sont courantes, mais vous oubliez d'écrire before_action ou en écrivez trop. Alors, utilisez skip_before_action et spécifiez la méthode à sauter au contraire. En gros, en ce qui concerne la méthode authorize!, Il semble bon d'ignorer même créer.
app/controllers/application_controller.rb
before_action :authorize!
private
Ajout d'une description qui est toujours appelée ci-dessus privé.
app/controllers/access_tokens_controller.rb
class AccessTokensController < ApplicationController
skip_before_action :authorize!, only: :create
Changez before_action et method.
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
skip_before_action :authorize!, only: [:index, :show]
Et n'oubliez pas de sauter article_controller. Je veux faire l'indexation et montrer sans authentification.
Exécutez maintenant le test pour voir si vous obtenez les mêmes résultats qu'avant le refactoring.
$ bundle exec rspec
Exécutez tous les tests et assurez-vous qu'ils sont tous verts.
Je vous remercie pour votre travail acharné. Avec cela, nous avons pu implémenter la fonction d'authentification des utilisateurs qui était notre objectif initial. Celles-ci peuvent être remplacées par l'utilisation d'un joyau appelé «dispositif», mais selon que vous connaissez le mécanisme ou non, la réponse aux problèmes d'authentification des utilisateurs changera et je pense que le degré de compréhension est complètement différent. .. La zone autour du jeton est très difficile à imaginer, et lors de l'utilisation de oauth, il y a encore des gemmes qui remplacent tout, donc le mécanisme a tendance à être en boîte noire. Donc, cette fois, j'ai utilisé l'authentification utilisateur comme celle-ci.
J'ai essayé d'implémenter l'API Rails avec TDD par RSpec. part3
Recommended Posts