Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e3a76b0
add new workshop outline
kettanaito Aug 26, 2025
435c91b
04/01 rename exercise
kettanaito Aug 26, 2025
c855e57
Merge branch 'main' into new-outline
kettanaito Nov 3, 2025
2cdf595
02/03: add passkey authentication test
kettanaito Nov 3, 2025
6763f16
use `test-passkey` for passkey tests
kettanaito Nov 3, 2025
9f5275c
add test case for failed passkey authentication
kettanaito Nov 4, 2025
347d791
refine existing exercises
kettanaito Nov 4, 2025
6183cc8
02/01: add basic auth test case
kettanaito Nov 4, 2025
1819a7f
add invalid credentials basic auth test case
kettanaito Nov 4, 2025
66876e1
add 2fa exercise skeleton
kettanaito Nov 4, 2025
dc31e9e
02/05: add exercise skeleton
kettanaito Nov 4, 2025
a6751ca
02/05: drop persona, seed user and verification directly
kettanaito Nov 4, 2025
ce3a467
drop captcha, draft 3rd-party providers
kettanaito Nov 4, 2025
4834b84
02/04: thoughts on testing 3rd-party auth
kettanaito Nov 4, 2025
d97bfcb
02/02: fix svg import build error
kettanaito Nov 6, 2025
419e7df
02/02: generate OTP from the same config
kettanaito Nov 6, 2025
9dd408d
02/04: add test cases
kettanaito Nov 7, 2025
227ab68
update `test-passkey` for proper `publicKey` type
kettanaito Nov 7, 2025
a8e7e8e
02/04: per-test app instances with `app-launcher`
kettanaito Nov 12, 2025
e5379aa
02/04: bind `prisma` to test cases
kettanaito Nov 12, 2025
dfe499e
02/04: run prod build for tests
kettanaito Nov 12, 2025
15fd192
attach database file to test artifacts
kettanaito Nov 14, 2025
a1dc5cf
add more e2e tests for performance a/b
kettanaito Nov 14, 2025
6d87ca8
remove oath exercise, add problems
kettanaito Nov 18, 2025
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
Prev Previous commit
Next Next commit
add 2fa exercise skeleton
  • Loading branch information
kettanaito committed Nov 4, 2025
commit 66876e12d83f087336942c699c9a690f291edb34
29 changes: 29 additions & 0 deletions exercises/02.authentication/02.solution.2fa/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
LITEFS_DIR="/litefs/data"
DATABASE_PATH="./prisma/data.db"
DATABASE_URL="file:./data.db?connection_limit=1"
CACHE_DATABASE_PATH="./other/cache.db"
SESSION_SECRET="dcfaa48e7e20dce686b8636a68fa26f0"
HONEYPOT_SECRET="super-duper-s3cret"
RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh"
SENTRY_DSN="your-dsn"

# this is set to a random value in the Dockerfile
INTERNAL_COMMAND_TOKEN="some-made-up-token"

# the mocks and some code rely on these two being prefixed with "MOCK_"
# if they aren't then the real github api will be attempted
GITHUB_CLIENT_ID="MOCK_GITHUB_CLIENT_ID"
GITHUB_CLIENT_SECRET="MOCK_GITHUB_CLIENT_SECRET"
GITHUB_TOKEN="MOCK_GITHUB_TOKEN"
GITHUB_REDIRECT_URI="https://example.com/auth/github/callback"

# set this to false to prevent search engines from indexing the website
# default to allow indexing for seo safety
ALLOW_INDEXING="true"

# Tigris Object Storage (S3-compatible) Configuration
AWS_ACCESS_KEY_ID="mock-access-key"
AWS_SECRET_ACCESS_KEY="mock-secret-key"
AWS_REGION="auto"
AWS_ENDPOINT_URL_S3="https://fly.storage.tigris.dev"
BUCKET_NAME="mock-bucket"
29 changes: 29 additions & 0 deletions exercises/02.authentication/02.solution.2fa/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
LITEFS_DIR="/litefs/data"
DATABASE_PATH="./prisma/data.db"
DATABASE_URL="file:./data.db?connection_limit=1"
CACHE_DATABASE_PATH="./other/cache.db"
SESSION_SECRET="super-duper-s3cret"
HONEYPOT_SECRET="super-duper-s3cret"
RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh"
SENTRY_DSN="your-dsn"

# this is set to a random value in the Dockerfile
INTERNAL_COMMAND_TOKEN="some-made-up-token"

# the mocks and some code rely on these two being prefixed with "MOCK_"
# if they aren't then the real github api will be attempted
GITHUB_CLIENT_ID="MOCK_GITHUB_CLIENT_ID"
GITHUB_CLIENT_SECRET="MOCK_GITHUB_CLIENT_SECRET"
GITHUB_TOKEN="MOCK_GITHUB_TOKEN"
GITHUB_REDIRECT_URI="https://example.com/auth/github/callback"

# set this to false to prevent search engines from indexing the website
# default to allow indexing for seo safety
ALLOW_INDEXING="true"

# Tigris Object Storage (S3-compatible) Configuration
AWS_ACCESS_KEY_ID="mock-access-key"
AWS_SECRET_ACCESS_KEY="mock-secret-key"
AWS_REGION="auto"
AWS_ENDPOINT_URL_S3="https://fly.storage.tigris.dev"
BUCKET_NAME="mock-bucket"
28 changes: 28 additions & 0 deletions exercises/02.authentication/02.solution.2fa/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
node_modules
.DS_store

/build
/server-build

.cache

/prisma/data.db
/prisma/data.db-journal
/tests/prisma

/test-results/
/playwright-report/
/playwright/.cache/
/tests/fixtures/email/
/tests/fixtures/uploaded/
/tests/fixtures/openimg/
/coverage

/other/cache.db

# Easy way to create temporary files/folders that won't accidentally be added to git
*.local.*

# generated files
/app/components/ui/icons
.react-router/
2 changes: 2 additions & 0 deletions exercises/02.authentication/02.solution.2fa/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
legacy-peer-deps=true
registry=https://registry.npmjs.org/
15 changes: 15 additions & 0 deletions exercises/02.authentication/02.solution.2fa/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
node_modules

/build
/public/build
/server-build
.env

/test-results/
/playwright-report/
/playwright/.cache/
/tests/fixtures/email/*.json
/coverage
/prisma/migrations

package-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"recommendations": [
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"prisma.prisma",
"qwtel.sqlite-viewer",
"yoavbls.pretty-ts-errors",
"github.vscode-github-actions"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"loader": {
"prefix": "/loader",
"scope": "typescriptreact,javascriptreact,typescript,javascript",
"body": [
"import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"",
"",
"export async function loader({ request }: Route.LoaderArgs) {",
" return {}",
"}",
],
},
"action": {
"prefix": "/action",
"scope": "typescriptreact,javascriptreact,typescript,javascript",
"body": [
"import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"",
"",
"export async function action({ request }: Route.ActionArgs) {",
" return {}",
"}",
],
},
"default": {
"prefix": "/default",
"scope": "typescriptreact,javascriptreact,typescript,javascript",
"body": [
"export default function ${TM_FILENAME_BASE/[^a-zA-Z0-9]*([a-zA-Z0-9])([a-zA-Z0-9]*)/${1:/capitalize}${2}/g}() {",
" return (",
" <div>",
" <h1>Unknown Route</h1>",
" </div>",
" )",
"}",
],
},
"headers": {
"prefix": "/headers",
"scope": "typescriptreact,javascriptreact,typescript,javascript",
"body": [
"import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"",
"export const headers: Route.HeadersFunction = ({ loaderHeaders }) => ({",
" 'Cache-Control': loaderHeaders.get('Cache-Control') ?? '',",
"})",
],
},
"links": {
"prefix": "/links",
"scope": "typescriptreact,javascriptreact,typescript,javascript",
"body": [
"import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"",
"",
"export const links: Route.LinksFunction = () => {",
" return []",
"}",
],
},
"meta": {
"prefix": "/meta",
"scope": "typescriptreact,javascriptreact,typescript,javascript",
"body": [
"import { type Route } from \"./+types/${TM_FILENAME_BASE}.ts\"",
"",
"export const meta: Route.MetaFunction = ({ data }) => [{",
" title: 'Title',",
"}]",
],
},
"shouldRevalidate": {
"prefix": "/shouldRevalidate",
"scope": "typescriptreact,javascriptreact,typescript,javascript",
"body": [
"import { type ShouldRevalidateFunctionArgs } from 'react-router'",
"",
"export function shouldRevalidate({ defaultShouldRevalidate }: ShouldRevalidateFunctionArgs) {",
" return defaultShouldRevalidate",
"}",
],
},
}
14 changes: 14 additions & 0 deletions exercises/02.authentication/02.solution.2fa/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"typescript.preferences.autoImportFileExcludePatterns": [
"@remix-run/server-runtime",
"express",
"@radix-ui/**",
"@react-email/**",
"node:stream/consumers",
"node:test",
"node:console"
],
"workbench.editorAssociations": {
"*.db": "sqlite-viewer.view"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { captureException } from '@sentry/react-router'
import { useEffect, type ReactElement } from 'react'
import {
type ErrorResponse,
isRouteErrorResponse,
useParams,
useRouteError,
} from 'react-router'
import { getErrorMessage } from '#app/utils/misc'

type StatusHandler = (info: {
error: ErrorResponse
params: Record<string, string | undefined>
}) => ReactElement | null

export function GeneralErrorBoundary({
defaultStatusHandler = ({ error }) => (
<p>
{error.status} {error.data}
</p>
),
statusHandlers,
unexpectedErrorHandler = (error) => <p>{getErrorMessage(error)}</p>,
}: {
defaultStatusHandler?: StatusHandler
statusHandlers?: Record<number, StatusHandler>
unexpectedErrorHandler?: (error: unknown) => ReactElement | null
}) {
const error = useRouteError()
const params = useParams()
const isResponse = isRouteErrorResponse(error)

if (typeof document !== 'undefined') {
console.error(error)
}

useEffect(() => {
if (isResponse) return

captureException(error)
}, [error, isResponse])

return (
<div className="text-h2 container flex items-center justify-center p-20">
{isResponse
? (statusHandlers?.[error.status] ?? defaultStatusHandler)({
error,
params,
})
: unexpectedErrorHandler(error)}
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const floatingToolbarClassName =
'absolute bottom-3 inset-x-3 flex items-center gap-2 rounded-lg bg-muted/80 p-4 pl-5 shadow-xl shadow-accent backdrop-blur-xs md:gap-4 md:pl-7 justify-end'
Loading