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

[BWA-108] Don't Show QR Code Scan Link When Camera Is Unauthorized #219

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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 AuthenticatorShared/Core/Platform/Services/CameraService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
/// A service that is used to manage camera access and use for the user.
///
protocol CameraService: AnyObject {
/// Checks the current camera authorization status without requesting authorization.
///
/// This method provides a synchronous alternative to `checkStatusOrRequestCameraAuthorization()` for
/// when we only need to determine the current status without requesting authorization.
///
/// - Returns: The current `CameraAuthorizationStatus` of the app.
///
func checkStatus() -> CameraAuthorizationStatus

/// Checks the current camera authorization status and requests authorization if necessary.
///
/// This method first checks the current camera authorization status granted to the app.
Expand Down Expand Up @@ -90,10 +99,14 @@
// MARK: - DefaultCamerAuthorizationService

extension DefaultCameraService: CameraService {
func checkStatusOrRequestCameraAuthorization() async -> CameraAuthorizationStatus {
let status = CameraAuthorizationStatus(
func checkStatus() -> CameraAuthorizationStatus {
CameraAuthorizationStatus(

Check warning on line 103 in AuthenticatorShared/Core/Platform/Services/CameraService.swift

View check run for this annotation

Codecov / codecov/patch

AuthenticatorShared/Core/Platform/Services/CameraService.swift#L102-L103

Added lines #L102 - L103 were not covered by tests
avAuthorizationStatus: AVCaptureDevice.authorizationStatus(for: .video)
)
}

Check warning on line 106 in AuthenticatorShared/Core/Platform/Services/CameraService.swift

View check run for this annotation

Codecov / codecov/patch

AuthenticatorShared/Core/Platform/Services/CameraService.swift#L106

Added line #L106 was not covered by tests

func checkStatusOrRequestCameraAuthorization() async -> CameraAuthorizationStatus {
let status = checkStatus()

Check warning on line 109 in AuthenticatorShared/Core/Platform/Services/CameraService.swift

View check run for this annotation

Codecov / codecov/patch

AuthenticatorShared/Core/Platform/Services/CameraService.swift#L108-L109

Added lines #L108 - L109 were not covered by tests

if status == .notDetermined {
return await requestCameraAuthorization()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class MockCameraService: CameraService {

// MARK: CameraService

func checkStatus() -> CameraAuthorizationStatus {
cameraAuthorizationStatus
}

func checkStatusOrRequestCameraAuthorization() async -> CameraAuthorizationStatus {
cameraAuthorizationStatus
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ final class AuthenticatorKeyCaptureCoordinator: Coordinator, HasStackNavigator {
services: services,
state: DefaultEntryState(
deviceSupportsCamera: services.cameraService.deviceSupportsCamera()
&& services.cameraService.checkStatus() == .authorized
)
)
let view = ManualEntryView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,42 @@ class AuthenticatorKeyCaptureCoordinatorTests: AuthenticatorTestCase {
XCTAssertTrue(didRun)
}

/// `navigate(to:)` with `.setupTotpManual` presents the manual entry view.
func test_navigateTo_setupTotpManual() throws {
/// `navigate(to:)` with `.setupTotpManual` presents the manual entry view. When the camera is
/// present but the user has denied access, it sets `deviceSupportsCamera` to `false`.
func test_navigateTo_setupTotpManual_cameraNotAuthorized() throws {
cameraService.deviceHasCamera = true
cameraService.cameraAuthorizationStatus = .denied
subject.navigate(to: .manualKeyEntry)

let action = try XCTUnwrap(stackNavigator.actions.last)
XCTAssertEqual(action.type, .replaced)
let view = action.view as? (any View)
XCTAssertNotNil(try? view?.inspect().find(ManualEntryView.self))
let view = try XCTUnwrap(action.view as? ManualEntryView)
XCTAssertFalse(view.store.state.deviceSupportsCamera)
}

/// `navigate(to:)` with `.setupTotpManual` presents the manual entry view. When the camera is
/// present and `.authorized`, it sets `deviceSupportsCamera` to `true`.
func test_navigateTo_setupTotpManual_cameraPresentAndAuthorized() throws {
cameraService.deviceHasCamera = true
cameraService.cameraAuthorizationStatus = .authorized
subject.navigate(to: .manualKeyEntry)

let action = try XCTUnwrap(stackNavigator.actions.last)
XCTAssertEqual(action.type, .replaced)
let view = try XCTUnwrap(action.view as? ManualEntryView)
XCTAssertTrue(view.store.state.deviceSupportsCamera)
}

/// `navigate(to:)` with `.setupTotpManual` presents the manual entry view. When the camera is
/// not present, it sets `deviceSupportsCamera` to `false`.
func test_navigateTo_setupTotpManual_noCamera() throws {
cameraService.deviceHasCamera = false
subject.navigate(to: .manualKeyEntry)

let action = try XCTUnwrap(stackNavigator.actions.last)
XCTAssertEqual(action.type, .replaced)
let view = try XCTUnwrap(action.view as? ManualEntryView)
XCTAssertFalse(view.store.state.deviceSupportsCamera)
}

/// `navigate(to:)` with `.setupTotpManual` presents the manual entry view.
Expand Down
Loading