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

Upgrade deps #7

Merged
merged 8 commits into from
Oct 30, 2024
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
2 changes: 2 additions & 0 deletions .github/workflows/preview.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20

- name: Enable Corepack
run: corepack enable
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20

- name: Enable Corepack
run: corepack enable
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ A repository with full stack WebAuthn API examples.
### How to start the project locally?

1. Initialize package manager:
Make sure you're running Node v20. Then initialize a package manager (corepack) by calling:
Make sure you're running Node v20. Then initialize a package manager:

```sh
corepack enable
corepack install
```

It finds `packageManager` field and install Yarn 4.
It finds `packageManager` field and installs Yarn 4.

2. Install dependencies:

Expand All @@ -51,4 +51,4 @@ A repository with full stack WebAuthn API examples.

## Have you a found a bug?

Please, open an [Github issue](https://github.com/cermakjiri/with-webauthn/issues/new/choose). Thank you. ❤️
Please, open a [Github issue](https://github.com/cermakjiri/with-webauthn/issues/new/choose). Thank you. ❤️
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextConfig } from 'next';
import { withSentryConfig } from '@sentry/nextjs';
import { config } from 'dotenv';
// @ts-check
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import type { dependencies } from 'package.json';

import './src/env/env.mjs';

Expand All @@ -11,38 +11,17 @@ if (process.env.NODE_ENV === 'development') {
});
}

/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
type Dependency = keyof typeof dependencies;

const nextConfig: NextConfig = {
reactStrictMode: true,
swcMinify: true,

i18n: {
locales: ['en'],
defaultLocale: 'en',
},

// https://dev.to/chromygabor/add-typescript-type-check-to-next-js-2nbb
webpack(config, options) {
// Do not run type checking twice:
if (options.dev && options.isServer) {
config.plugins.push(
new ForkTsCheckerWebpackPlugin({
typescript: {
memoryLimit: 4096,
},
}),
);
}

return config;
},

/**
* @type {(keyof (typeof import('./package.json'))['dependencies'])[]}
*/
transpilePackages: ['@workspace/ui', '@workspace/logger'],
transpilePackages: ['@workspace/ui', '@workspace/logger'] satisfies Dependency[],

redirects: async () => {
return [];
Expand Down
31 changes: 15 additions & 16 deletions examples/simplewebauthn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "next dev",
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "eslint-lint --config=eslint.config.mjs './src/**/*.{ts,tsx}'",
Expand All @@ -13,37 +13,36 @@
},
"dependencies": {
"@ackee/antonio-core": "5.0.0",
"@hookform/resolvers": "3.9.0",
"@sentry/nextjs": "8.33.1",
"@simplewebauthn/browser": "10.0.0",
"@simplewebauthn/server": "10.0.1",
"@hookform/resolvers": "3.9.1",
"@sentry/nextjs": "8.35.0",
"@simplewebauthn/browser": "11.0.0",
"@simplewebauthn/server": "11.0.0",
"@t3-oss/env-nextjs": "0.11.1",
"@tanstack/react-query": "5.59.8",
"@tanstack/react-query": "5.59.16",
"@workspace/logger": "workspace:*",
"@workspace/ui": "workspace:*",
"cookie": "1.0.0",
"firebase": "10.14.0",
"firebase-admin": "12.6.0",
"next": "14.2.15",
"cookie": "1.0.1",
"firebase": "11.0.1",
"firebase-admin": "12.7.0",
"next": "15.0.2",
"normalize.css": "8.0.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-intl": "6.7.2",
"react-toastify": "10.0.5",
"react-intl": "6.8.4",
"react-toastify": "10.0.6",
"reset.css": "2.0.2",
"string-ts": "2.2.0",
"zod": "3.23.8"
},
"devDependencies": {
"@tanstack/react-query-devtools": "5.59.8",
"@tanstack/react-query-devtools": "5.59.16",
"@tooling/eslint": "workspace:*",
"@tooling/madge": "workspace:*",
"@tooling/prettier": "workspace:*",
"@tooling/typescript": "workspace:*",
"@types/react": "18.3.11",
"@types/react-dom": "18.3.0",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"browserslist-config-custom": "workspace:*",
"fork-ts-checker-webpack-plugin": "9.0.2",
"type-fest": "4.26.1",
"typescript": "5.6.3"
},
Expand Down
12 changes: 6 additions & 6 deletions examples/simplewebauthn/src/instrumentation.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import * as Sentry from '@sentry/nextjs';

export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
}
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('../sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
}
if (process.env.NEXT_RUNTIME === 'edge') {
await import('../sentry.edge.config');
}
}

export const onRequestError = Sentry.captureRequestError;
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const LoginWithPasskey = () => {
>
<FieldsStack>
<FormError />
<EmailField<LoginFormValues> name='email' autoComplete='email webauthn' />
<EmailField<LoginFormValues> name='email' autoComplete='username webauthn' />
</FieldsStack>

<SubmitButton sx={{ mt: 3 }} endIcon={<Fingerprint />}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ export function useConditionalMediation() {

logger.info('/authentication/with-firebase/login/options', publicKeyOptions);

const result = await startAuthentication(publicKeyOptions, true);
const result = await startAuthentication({
optionsJSON: publicKeyOptions,
useBrowserAutofill: true,
});

logger.info('startAuthentication', result);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export function useLoginWithPasskey(): FormProps<LoginFormSchema, LoginFormValue

logger.info('/authentication/with-firebase/login/options', { publicKeyOptions });

const result = await startAuthentication(publicKeyOptions);
const result = await startAuthentication({
optionsJSON: publicKeyOptions,
});

logger.info('Authentication result:', result);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export function useAddPasskey() {

logger.info('/authentication/with-firebase/link/options', publicKeyOptions);

const result = await startRegistration(publicKeyOptions);
const result = await startRegistration({
optionsJSON: publicKeyOptions,
});

logger.info('Registration result:', result);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export function useRemovePasskey(openDialog: (data: PostRemovalDialogProps['data

logger.info('/authentication/with-firebase/remove/options', publicKeyOptions);

const result = await startAuthentication(publicKeyOptions);
const result = await startAuthentication({
optionsJSON: publicKeyOptions,
});

logger.info('Authentication result:', result);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ export function useRegisterWithPasskey(): FormProps<RegisterFormSchema, Register

logger.info('/authentication/with-firebase/register/options', { publicKeyOptions, username });

const result = await startRegistration(publicKeyOptions);
const result = await startRegistration({
optionsJSON: publicKeyOptions,
});

logger.info('Registration result', result);

Expand Down
20 changes: 10 additions & 10 deletions examples/simplewebauthn/src/pages/_error.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import * as Sentry from "@sentry/nextjs";
import Error from "next/error";
import Error from 'next/error';
import * as Sentry from '@sentry/nextjs';

const CustomErrorComponent = (props) => {
return <Error statusCode={props.statusCode} />;
const CustomErrorComponent = props => {
return <Error statusCode={props.statusCode} />;
};

CustomErrorComponent.getInitialProps = async (contextData) => {
// In case this is running in a serverless function, await this in order to give Sentry
// time to send the error before the lambda exits
await Sentry.captureUnderscoreErrorException(contextData);
CustomErrorComponent.getInitialProps = async contextData => {
// In case this is running in a serverless function, await this in order to give Sentry
// time to send the error before the lambda exits
await Sentry.captureUnderscoreErrorException(contextData);

// This will contain the status code of the response
return Error.getInitialProps(contextData);
// This will contain the status code of the response
return Error.getInitialProps(contextData);
};

export default CustomErrorComponent;
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
timeout: registrationOptions.timeout!,
challenge,
username: user.username,
webAuthnUserId: registrationOptions.user.id,
});

res.status(200).json({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)

logger.debug('verifiedRegistrationResponse', verifiedRegistrationResponse);

const { webAuthnUserId } = challengeSession;
const userId = idTokenResult.uid;

await addUserPasskey(userId, {
webAuthnUserId,
registrationResponse,
verifiedRegistrationInfo: verifiedRegistrationResponse.registrationInfo,
});
await addUserPasskey(userId, verifiedRegistrationResponse.registrationInfo);

res.status(200).end();
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<

challenge,

/**
* We might not want to provide list of available passkeys to unauthenticated users for privacy reasons:
* - https://w3c.github.io/webauthn/#sctn-credential-id-privacy-leak
* - https://w3c.github.io/webauthn/#sctn-unprotected-account-detection (This is not relevent for this demo but, I believe, developers should be aware of this when implementing WebAuthn in production.)
* // TODO: implement protection against privacy leaks above
*/
allowCredentials: passkeys.map(({ credentialId, transports }) => ({
id: credentialId,
transports,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// This might happen if the user has removed the passkey from the account
// but the private passkey source is still stored in a keychain / password manager.
if (!passkey) {
/**
* Or we could return registration options and pass it back with a custom error code,
* so client can start registration process right away.
* Depends on a use-case, of course.
*/
return res.status(400).end('Passkey not found.');
// Yes, this message would have been better however it could be a security risk (i.e. username enumeration):
// Checkout https://w3c.github.io/webauthn/#sctn-username-enumeration.
// return res.status(400).end('Passkey not found.');
return res.status(401).end('User not verified.');
}

const { transports, credentialId, credentialPublicKey, credentialCounter } = passkey;
Expand All @@ -48,11 +46,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
expectedRPID: challengeSession.rpId,
expectedOrigin: challengeSession.origin,
expectedChallenge: challengeSession.challenge,
authenticator: {
credentialPublicKey: new Uint8Array(base64URLStringToBuffer(credentialPublicKey)),
credential: {
publicKey: new Uint8Array(base64URLStringToBuffer(credentialPublicKey)),
counter: credentialCounter,
transports,
credentialID: credentialId,
id: credentialId,
},
requireUserVerification: true,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
timeout: registrationOptions.timeout!,
challenge,
username: body.username,
webAuthnUserId: registrationOptions.user.id,
});

res.status(200).json({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// Just an example what you can do with the result, not needed for this current authentication process itself
// parseRegistrationResponse(registrationResponse);

const { username, webAuthnUserId } = challengeSession;
const { username } = challengeSession;

const existingUser = await findUserByUsername(username);

if (existingUser) {
return res.status(400).end('User already exists.');
}

const userId = await createUserPasskey(username, {
webAuthnUserId,
registrationResponse,
verifiedRegistrationInfo: verifiedRegistrationResponse.registrationInfo,
});
const userId = await createUserPasskey(username, verifiedRegistrationResponse.registrationInfo);

/**
* Creates a new Firebase custom token (JWT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
expectedRPID: challengeSession.rpId,
expectedOrigin: challengeSession.origin,
expectedChallenge: challengeSession.challenge,
authenticator: {
credentialPublicKey: new Uint8Array(base64URLStringToBuffer(credentialPublicKey)),
credential: {
publicKey: new Uint8Array(base64URLStringToBuffer(credentialPublicKey)),
counter: credentialCounter,
transports,
credentialID: credentialId,
id: credentialId,
},
requireUserVerification: true,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ export type InitializeChallengeSessionProps = {
| {
type: 'attestation';
username: string;
webAuthnUserId: string;
}
| {
type: 'assertion';
challenge: ArrayBuffer;
}
);

Expand Down
Loading