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

Add client authn verification page #929

Merged
merged 9 commits into from
Jun 29, 2022
17 changes: 17 additions & 0 deletions app/pages/DeviceAuthSuccessPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Success16Icon } from '@oxide/ui'

/**
* Device authorization success page
*/
export default function DeviceAuthSuccessPage() {
return (
<div className="space-y-4 max-w-sm text-center">
<h1 className="text-sans-2xl">Device authentication</h1>
<h2 className="text-sans-3xl flex items-center text-accent justify-center">
<Success16Icon width={40} height={40} className="mr-3 text-accent" />
Success!
</h2>
<p>Your device is now logged in.</p>
</div>
)
}
48 changes: 48 additions & 0 deletions app/pages/DeviceAuthVerifyPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useNavigate, useSearchParams } from 'react-router-dom'

import { useApiMutation } from '@oxide/api'
import { Button, Warning12Icon } from '@oxide/ui'

import { useToast } from '../hooks'

/**
* Device authorization verification page
*/
export default function DeviceAuthVerifyPage() {
const [searchParams] = useSearchParams()
const navigate = useNavigate()
const addToast = useToast()
const confirmPost = useApiMutation('deviceAuthConfirm', {
onSuccess: () => {
navigate('/device/success')
},
onError: () => {
addToast({
title: 'Token denied',
icon: <Warning12Icon />,
variant: 'error',
timeout: 4000,
})
},
})
const userCode = searchParams.get('user_code')

return (
<div className="space-y-4 max-w-sm text-center">
<h1 className="text-sans-2xl">Device authentication</h1>
<p>Make sure this code matches the one shown on the device you are authenticating.</p>
<h2 className="text-sans-3xl border p-4">{userCode}</h2>
<Button
className="w-full"
disabled={confirmPost.isLoading || confirmPost.isSuccess || !userCode}
onClick={() => {
// we know `userCode` is non-null because the button is disabled
// otherwise, but let's make TS happy
if (userCode) confirmPost.mutate({ body: { userCode } })
}}
>
Log in on device
</Button>
</div>
)
}
7 changes: 7 additions & 0 deletions app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import OrgLayout from './layouts/OrgLayout'
import ProjectLayout from './layouts/ProjectLayout'
import RootLayout from './layouts/RootLayout'
import SettingsLayout from './layouts/SettingsLayout'
import DeviceAuthSuccessPage from './pages/DeviceAuthSuccessPage'
import DeviceAuthVerifyPage from './pages/DeviceAuthVerifyPage'
import LoginPage from './pages/LoginPage'
import NotFound from './pages/NotFound'
import { OrgAccessPage } from './pages/OrgAccessPage'
Expand Down Expand Up @@ -44,6 +46,11 @@ export const Router = () => (
<Route index element={<LoginPage />} />
</Route>

<Route path="device" element={<AuthLayout />}>
<Route path="verify" element={<DeviceAuthVerifyPage />} />
<Route path="success" element={<DeviceAuthSuccessPage />} />
</Route>

<Route index element={<Navigate to="/orgs" replace />} />

<Route path="orgs" errorElement={<RouterDataErrorBoundary />}>
Expand Down
10 changes: 10 additions & 0 deletions libs/api-mocks/msw/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -875,4 +875,14 @@ export const handlers = [
rest.get<never, never, Json<Api.UserResultsPage> | GetErr>('/api/users', (req, res) => {
return res(json(paginated(req.url.search, db.users)))
}),

rest.post<Json<Api.DeviceAuthVerify>, never, PostErr>(
'/api/device/confirm',
(req, res, ctx) => {
if (req.body.user_code === 'BADD-CODE') {
return res(ctx.status(404))
}
return res(ctx.status(200))
}
),
]