Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 2fa pages #4224

Merged
merged 5 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions app/controllers/api/nextv1/publishers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@ class Api::Nextv1::PublishersController < Api::Nextv1::BaseController

def me
publisher_hash = JSON.parse(current_publisher.to_json)
publisher_hash["two_factor_enabled"] = two_factor_enabled?(current_publisher)
render(json: publisher_hash.to_json, status: 200)
response_data = {
**publisher_hash,
two_factor_enabled: two_factor_enabled?(current_publisher)
}
render(json: response_data.to_json, status: 200)
end

def security
response_data = {
u2f_enabled: u2f_enabled?(current_publisher),
totp_enabled: totp_enabled?(current_publisher),
u2f_registrations: current_publisher.u2f_registrations
}

render(json: response_data.to_json, status: 200)
end

def update
Expand Down
45 changes: 45 additions & 0 deletions app/controllers/api/nextv1/totp_registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
class Api::Nextv1::TotpRegistrationsController < Api::Nextv1::BaseController
include QrCodeHelper
include TwoFactorRegistration
include PendingActions

def new
@totp_registration = TotpRegistration.new secret: ROTP::Base32.random_base32
@provisioning_url = @totp_registration.totp.provisioning_uri(current_publisher.email)
Expand All @@ -11,4 +14,46 @@ def new

render(json: response_data.to_json, status: 200)
end

class AddTOTP < StepUpAction
mattwilliams85 marked this conversation as resolved.
Show resolved Hide resolved
call do |publisher_id, password, totp_registration_params|
current_publisher = Publisher.find(publisher_id)
totp_registration = TotpRegistration.new totp_registration_params

if totp_registration.totp.verify(password, drift_ahead: 60, drift_behind: 60, at: Time.now - 30)
current_publisher.totp_registration.destroy! if current_publisher.totp_registration.present?
totp_registration.publisher = current_publisher
totp_registration.save!

logout_everybody_else!(current_publisher)

render(json: {}, status: 200)
else
render(json: {errors: totp_registration.errors}, status: 400)
end
end
end

def create
AddTOTP.new(current_publisher.id, params[:totp_password], totp_registration_params.to_h).step_up! self
end

class RemoveTOTP < StepUpAction
mattwilliams85 marked this conversation as resolved.
Show resolved Hide resolved
call do |publisher_id|
current_publisher = Publisher.find(publisher_id)
current_publisher.totp_registration.destroy! if current_publisher.totp_registration.present?
end
end

def destroy
RemoveTOTP.new(current_publisher.id).step_up! self

render(json: {}, status: 200)
end

private

def totp_registration_params
params.require(:totp_registration).permit(:secret)
end
end
64 changes: 64 additions & 0 deletions app/controllers/api/nextv1/u2f_registrations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class Api::Nextv1::U2fRegistrationsController < Api::Nextv1::BaseController
include Logout
include TwoFactorRegistration

include PendingActions

before_action :authenticate_publisher!

def new
@u2f_registration = U2fRegistration.new
publisher = current_publisher

@webauthn_options = WebAuthn::Credential.options_for_create(
user: {id: publisher.id, name: publisher.email},
exclude: publisher.u2f_registrations.map { |c| c.key_handle }.compact
)

session[:creation_challenge] = @webauthn_options.challenge

render(json: @webauthn_options.to_json, status: 200)
end

class AddU2F < StepUpAction
mattwilliams85 marked this conversation as resolved.
Show resolved Hide resolved
call do |publisher_id, webauthn_response, name, challenge|
current_publisher = Publisher.find(publisher_id)
result = TwoFactorAuth::WebauthnRegistrationService.build.call(publisher: current_publisher,
webauthn_response: webauthn_response,
name: name,
challenge: challenge)

case result
when BSuccess
logout_everybody_else!(current_publisher)
render(json: {}, status: 200)
when BFailure
render(json: {errors: ["Webauthn registration failed"]}, status: 400) && return
else
raise result
end
end
end

def create
AddU2F.new(current_publisher.id,
params[:webauthn_response],
params.require(:u2f_registration).permit(:name)[:name],
session[:creation_challenge]).step_up! self
end

class RemoveU2F < StepUpAction
mattwilliams85 marked this conversation as resolved.
Show resolved Hide resolved
call do |publisher_id, u2f_id|
current_publisher = Publisher.find(publisher_id)

u2f_registration = current_publisher.u2f_registrations.find(u2f_id)
u2f_registration.destroy
end
end

def destroy
RemoveU2F.new(current_publisher.id, params[:id]).step_up! self

render(json: {}, status: 200)
end
end
9 changes: 8 additions & 1 deletion app/services/pending_actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,14 @@ def execute! context
def step_up! context
if context.two_factor_enabled?(current_publisher)
save! context
context.redirect_to context.two_factor_authentications_path
if context.class.name.include?("Nextv1")
execute! context
# context.render(json: {
# error: '2fa_required'
# }, status: 200)
else
context.redirect_to context.two_factor_authentications_path
end
else
execute! context
end
Expand Down
9 changes: 9 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,18 @@
namespace :nextv1, defaults: {format: :json} do
resources :publishers, only: [:update, :destroy]
get "publishers/me", to: "publishers#me"
get "publishers/security", to: "publishers#security"

namespace :totp_registrations do
get :new
post :create
delete :destroy
end

namespace :u2f_registrations do
get :new
post :create
delete :destroy
end
end

Expand Down
84 changes: 78 additions & 6 deletions nextjs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
"typecheck": "tsc --noEmit --incremental false"
},
"dependencies": {
"@brave/leo": "github:brave/leo#86be9379ea55a7e32b56beee05a4e57caa1c0da2",
"@brave/leo": "github:brave/leo#80f2230bcfb7d2a87e7025a4e26c502d900fd938",
"@github/webauthn-json": "^2.1.1",
"axios": "^1.5.0",
"clsx": "^2.0.0",
"moment": "^2.29.4",
"next": "^13.4.12",
"next-intl": "^3.0.0-beta.9",
"react": "^18.2.0",
Expand Down
29 changes: 29 additions & 0 deletions nextjs/src/app/[locale]/publishers/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export default function Loading() {
// You can add any UI inside Loading, including a Skeleton.
return (
<div className='absolute flex h-full w-full justify-center'>
<div className='mt-[200px] h-[100px] w-[100px]'>
<svg className='animate-spin' viewBox='0 0 50 50'>
<circle
cx='25'
cy='25'
r='20'
fill='none'
stroke='#eee'
stroke-width='5'
/>
<circle
strokeLinecap='round'
className='animate-dash'
cx='25'
cy='25'
r='20'
fill='none'
stroke='#fd2f00'
stroke-width='5'
/>
</svg>
</div>
</div>
);
}
Loading
Loading