diff --git a/app/pages/DeviceAuthSuccessPage.tsx b/app/pages/DeviceAuthSuccessPage.tsx
new file mode 100644
index 000000000..dd90a992c
--- /dev/null
+++ b/app/pages/DeviceAuthSuccessPage.tsx
@@ -0,0 +1,17 @@
+import { Success16Icon } from '@oxide/ui'
+
+/**
+ * Device authorization success page
+ */
+export default function DeviceAuthSuccessPage() {
+ return (
+
+
Device authentication
+
+
+ Success!
+
+
Your device is now logged in.
+
+ )
+}
diff --git a/app/pages/DeviceAuthVerifyPage.tsx b/app/pages/DeviceAuthVerifyPage.tsx
new file mode 100644
index 000000000..5d01f17b1
--- /dev/null
+++ b/app/pages/DeviceAuthVerifyPage.tsx
@@ -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: ,
+ variant: 'error',
+ timeout: 4000,
+ })
+ },
+ })
+ const userCode = searchParams.get('user_code')
+
+ return (
+
+
Device authentication
+
Make sure this code matches the one shown on the device you are authenticating.
+
{userCode}
+
+
+ )
+}
diff --git a/app/routes.tsx b/app/routes.tsx
index 733bfda42..fff239bee 100644
--- a/app/routes.tsx
+++ b/app/routes.tsx
@@ -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'
@@ -44,6 +46,11 @@ export const Router = () => (
} />
+ }>
+ } />
+ } />
+
+
} />
}>
diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts
index d2d2072bf..0243b532d 100644
--- a/libs/api-mocks/msw/handlers.ts
+++ b/libs/api-mocks/msw/handlers.ts
@@ -875,4 +875,14 @@ export const handlers = [
rest.get | GetErr>('/api/users', (req, res) => {
return res(json(paginated(req.url.search, db.users)))
}),
+
+ rest.post, 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))
+ }
+ ),
]