mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-20 02:10:15 +01:00
fix: working code + updated my package passport-simple-webauthn2
This commit is contained in:
parent
1e1b865f4f
commit
8ea085ee25
4 changed files with 119 additions and 161 deletions
|
|
@ -96,7 +96,7 @@
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"passport-simple-webauthn2": "^3.0.5",
|
"passport-simple-webauthn2": "^3.2.0",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
"tiktoken": "^1.0.15",
|
"tiktoken": "^1.0.15",
|
||||||
"traverse": "^0.6.7",
|
"traverse": "^0.6.7",
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ const AppService = require('./services/AppService');
|
||||||
const staticCache = require('./utils/staticCache');
|
const staticCache = require('./utils/staticCache');
|
||||||
const noIndex = require('./middleware/noIndex');
|
const noIndex = require('./middleware/noIndex');
|
||||||
const routes = require('./routes');
|
const routes = require('./routes');
|
||||||
const { WebAuthnStrategy } = require('passport-simple-webauthn2');
|
|
||||||
const { mongoUserStore, mongoChallengeStore } = require('~/cache');
|
const { mongoUserStore, mongoChallengeStore } = require('~/cache');
|
||||||
|
const { WebAuthnStrategy } = require('passport-simple-webauthn2');
|
||||||
|
|
||||||
const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION } = process.env ?? {};
|
const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION } = process.env ?? {};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
type PasskeyAuthProps = {
|
type PasskeyAuthProps = {
|
||||||
mode: 'login' | 'register';
|
mode: 'login' | 'register';
|
||||||
onBack?: () => void;
|
onBack?: () => void; // Optional callback to return to normal login/register view
|
||||||
};
|
};
|
||||||
|
|
||||||
const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
|
|
@ -11,47 +11,25 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const fetchWithError = async (url: string, options: RequestInit) => {
|
// --- PASSKEY LOGIN FLOW ---
|
||||||
const response = await fetch(url, options);
|
async function handlePasskeyLogin() {
|
||||||
if (!response.ok) {
|
|
||||||
const errorData = await response.json().catch(() => ({}));
|
|
||||||
throw new Error(errorData.error || 'Network request failed');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
};
|
|
||||||
|
|
||||||
const base64URLToArrayBuffer = (base64url: string): ArrayBuffer => {
|
|
||||||
const padding = '='.repeat((4 - (base64url.length % 4)) % 4);
|
|
||||||
const base64 = (base64url + padding).replace(/-/g, '+').replace(/_/g, '/');
|
|
||||||
const binary = atob(base64);
|
|
||||||
return Uint8Array.from(binary, (c) => c.charCodeAt(0)).buffer;
|
|
||||||
};
|
|
||||||
|
|
||||||
const arrayBufferToBase64URL = (buffer: ArrayBuffer): string => {
|
|
||||||
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
|
|
||||||
.replace(/\+/g, '-')
|
|
||||||
.replace(/\//g, '_')
|
|
||||||
.replace(/=+$/, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Passkey Login Flow
|
|
||||||
*/
|
|
||||||
const handlePasskeyLogin = async () => {
|
|
||||||
if (!email) {
|
if (!email) {
|
||||||
alert('Email is required for login.');
|
return alert('Email is required for login.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// 1. Fetch login challenge
|
const challengeResponse = await fetch(
|
||||||
const options = await fetchWithError(
|
|
||||||
`/webauthn/login?email=${encodeURIComponent(email)}`,
|
`/webauthn/login?email=${encodeURIComponent(email)}`,
|
||||||
{ method: 'GET' },
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
if (!challengeResponse.ok) {
|
||||||
// 2. Convert challenge & credential IDs
|
const errorData = await challengeResponse.json();
|
||||||
|
throw new Error(errorData.error || 'Failed to get challenge');
|
||||||
|
}
|
||||||
|
const options = await challengeResponse.json();
|
||||||
options.challenge = base64URLToArrayBuffer(options.challenge);
|
options.challenge = base64URLToArrayBuffer(options.challenge);
|
||||||
if (options.allowCredentials) {
|
if (options.allowCredentials) {
|
||||||
options.allowCredentials = options.allowCredentials.map((cred: any) => ({
|
options.allowCredentials = options.allowCredentials.map((cred: any) => ({
|
||||||
|
|
@ -59,49 +37,33 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
id: base64URLToArrayBuffer(cred.id),
|
id: base64URLToArrayBuffer(cred.id),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
const credential = await navigator.credentials.get({ publicKey: options });
|
||||||
// 3. Request credential
|
|
||||||
const credential = await navigator.credentials.create({ publicKey: options });
|
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
new Error('Failed to obtain credential');
|
throw new Error('Failed to obtain credential');
|
||||||
}
|
}
|
||||||
|
const authenticationResponse = {
|
||||||
// 4. Build credential object
|
id: credential.id,
|
||||||
const { id, rawId, response, type } = credential;
|
rawId: arrayBufferToBase64URL(credential.rawId),
|
||||||
const authResponse = {
|
type: credential.type,
|
||||||
id,
|
|
||||||
rawId: arrayBufferToBase64URL(rawId),
|
|
||||||
type,
|
|
||||||
response: {
|
response: {
|
||||||
authenticatorData: arrayBufferToBase64URL(
|
authenticatorData: arrayBufferToBase64URL((credential.response as any).authenticatorData),
|
||||||
(response as AuthenticatorAssertionResponse).authenticatorData
|
clientDataJSON: arrayBufferToBase64URL((credential.response as any).clientDataJSON),
|
||||||
),
|
signature: arrayBufferToBase64URL((credential.response as any).signature),
|
||||||
clientDataJSON: arrayBufferToBase64URL(
|
userHandle: (credential.response as any).userHandle
|
||||||
(response as AuthenticatorAssertionResponse).clientDataJSON
|
? arrayBufferToBase64URL((credential.response as any).userHandle)
|
||||||
),
|
|
||||||
signature: arrayBufferToBase64URL(
|
|
||||||
(response as AuthenticatorAssertionResponse).signature
|
|
||||||
),
|
|
||||||
userHandle: (response as AuthenticatorAssertionResponse).userHandle
|
|
||||||
? arrayBufferToBase64URL(
|
|
||||||
(response as AuthenticatorAssertionResponse).userHandle!
|
|
||||||
)
|
|
||||||
: null,
|
: null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const loginCallbackResponse = await fetch('/webauthn/login', {
|
||||||
// 5. Send credential to server for verification
|
|
||||||
const result = await fetchWithError('/webauthn/login', {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email, credential: authResponse }),
|
body: JSON.stringify({ email, credential: authenticationResponse }),
|
||||||
});
|
});
|
||||||
|
const result = await loginCallbackResponse.json();
|
||||||
if (result?.user) {
|
if (result.user) {
|
||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
} else {
|
} else {
|
||||||
new Error(result?.error || 'Authentication failed');
|
throw new Error(result.error || 'Authentication failed');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Passkey login error:', error);
|
console.error('Passkey login error:', error);
|
||||||
|
|
@ -109,26 +71,27 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
// --- PASSKEY REGISTRATION FLOW ---
|
||||||
* Passkey Registration Flow
|
async function handlePasskeyRegister() {
|
||||||
*/
|
|
||||||
const handlePasskeyRegister = async () => {
|
|
||||||
if (!email) {
|
if (!email) {
|
||||||
alert('Email is required for registration.');
|
return alert('Email is required for registration.');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// 1. Fetch registration challenge
|
const challengeResponse = await fetch(
|
||||||
const options = await fetchWithError(
|
|
||||||
`/webauthn/register?email=${encodeURIComponent(email)}`,
|
`/webauthn/register?email=${encodeURIComponent(email)}`,
|
||||||
{ method: 'GET' },
|
{
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
if (!challengeResponse.ok) {
|
||||||
// 2. Convert challenge & credential IDs
|
const errorData = await challengeResponse.json();
|
||||||
|
throw new Error(errorData.error || 'Failed to get challenge');
|
||||||
|
}
|
||||||
|
const options = await challengeResponse.json();
|
||||||
options.challenge = base64URLToArrayBuffer(options.challenge);
|
options.challenge = base64URLToArrayBuffer(options.challenge);
|
||||||
options.user.id = base64URLToArrayBuffer(options.user.id);
|
options.user.id = base64URLToArrayBuffer(options.user.id);
|
||||||
if (options.excludeCredentials) {
|
if (options.excludeCredentials) {
|
||||||
|
|
@ -137,41 +100,29 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
id: base64URLToArrayBuffer(cred.id),
|
id: base64URLToArrayBuffer(cred.id),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Request credential creation
|
|
||||||
const credential = await navigator.credentials.create({ publicKey: options });
|
const credential = await navigator.credentials.create({ publicKey: options });
|
||||||
|
|
||||||
if (!credential) {
|
if (!credential) {
|
||||||
new Error('Failed to create credential');
|
throw new Error('Failed to create credential');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Build registration object
|
|
||||||
const { id, rawId, response, type } = credential;
|
|
||||||
const registrationResponse = {
|
const registrationResponse = {
|
||||||
id,
|
id: credential.id,
|
||||||
rawId: arrayBufferToBase64URL(rawId),
|
rawId: arrayBufferToBase64URL(credential.rawId),
|
||||||
type,
|
type: credential.type,
|
||||||
response: {
|
response: {
|
||||||
clientDataJSON: arrayBufferToBase64URL(
|
clientDataJSON: arrayBufferToBase64URL((credential.response as any).clientDataJSON),
|
||||||
(response as AuthenticatorAttestationResponse).clientDataJSON
|
attestationObject: arrayBufferToBase64URL((credential.response as any).attestationObject),
|
||||||
),
|
|
||||||
attestationObject: arrayBufferToBase64URL(
|
|
||||||
(response as AuthenticatorAttestationResponse).attestationObject
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const registerCallbackResponse = await fetch('/webauthn/register', {
|
||||||
// 5. Send credential to server for verification
|
|
||||||
const result = await fetchWithError('/webauthn/register', {
|
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ email, credential: registrationResponse }),
|
body: JSON.stringify({ email, credential: registrationResponse }),
|
||||||
});
|
});
|
||||||
|
const result = await registerCallbackResponse.json();
|
||||||
if (result?.user) {
|
if (result.user) {
|
||||||
window.location.href = '/login';
|
window.location.href = '/login';
|
||||||
} else {
|
} else {
|
||||||
new Error(result?.error || 'Registration failed');
|
throw new Error(result.error || 'Registration failed');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Passkey registration error:', error);
|
console.error('Passkey registration error:', error);
|
||||||
|
|
@ -179,7 +130,7 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
@ -195,7 +146,7 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className="relative mb-4">
|
<div className="relative mb-4">
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="text"
|
||||||
id="passkey-email"
|
id="passkey-email"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
aria-label={localize('com_auth_email')}
|
aria-label={localize('com_auth_email')}
|
||||||
|
|
@ -211,37 +162,29 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
<label
|
<label
|
||||||
htmlFor="passkey-email"
|
htmlFor="passkey-email"
|
||||||
className="
|
className="
|
||||||
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform
|
absolute start-3 top-1.5 z-10 origin-[0] -translate-y-4 scale-75 transform bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
|
||||||
bg-surface-primary px-2 text-sm text-text-secondary-alt duration-200
|
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2 peer-placeholder-shown:scale-100
|
||||||
peer-placeholder-shown:top-1/2 peer-placeholder-shown:-translate-y-1/2
|
peer-focus:top-1.5 peer-focus:-translate-y-4 peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-600 dark:peer-focus:text-green-500
|
||||||
peer-placeholder-shown:scale-100 peer-focus:top-1.5 peer-focus:-translate-y-4
|
rtl:peer-focus:left-auto rtl:peer-focus:translate-x-1/4
|
||||||
peer-focus:scale-75 peer-focus:px-2 peer-focus:text-green-600
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{localize('com_auth_email_address')}
|
{localize('com_auth_email_address')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
className="
|
className="w-full rounded-2xl bg-green-600 px-4 py-3 text-sm font-medium text-white transition-colors hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 disabled:opacity-50"
|
||||||
w-full rounded-2xl bg-green-600 px-4 py-3 text-sm
|
|
||||||
font-medium text-white transition-colors hover:bg-green-700
|
|
||||||
focus:outline-none focus:ring-2 focus:ring-green-500
|
|
||||||
focus:ring-offset-2 disabled:opacity-50
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{loading
|
{loading
|
||||||
? localize('com_auth_loading')
|
? localize('com_auth_loading')
|
||||||
: localize(
|
: localize(
|
||||||
mode === 'login'
|
mode === 'login'
|
||||||
? 'com_auth_passkey_login'
|
? 'com_auth_passkey_login'
|
||||||
: 'com_auth_passkey_register'
|
: 'com_auth_passkey_register',
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{onBack && (
|
{onBack && (
|
||||||
<div className="mt-4 text-center">
|
<div className="mt-4 text-center">
|
||||||
<button
|
<button
|
||||||
|
|
@ -251,7 +194,7 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
{localize(
|
{localize(
|
||||||
mode === 'login'
|
mode === 'login'
|
||||||
? 'com_auth_back_to_login'
|
? 'com_auth_back_to_login'
|
||||||
: 'com_auth_back_to_register'
|
: 'com_auth_back_to_register',
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -260,4 +203,19 @@ const PasskeyAuth: React.FC<PasskeyAuthProps> = ({ mode, onBack }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PasskeyAuth;
|
export default PasskeyAuth;
|
||||||
|
|
||||||
|
// Utility functions for base64url conversion
|
||||||
|
function base64URLToArrayBuffer(base64url: string): ArrayBuffer {
|
||||||
|
const padding = '='.repeat((4 - (base64url.length % 4)) % 4);
|
||||||
|
const base64 = (base64url + padding).replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
const binary = atob(base64);
|
||||||
|
return Uint8Array.from(binary, (c) => c.charCodeAt(0)).buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayBufferToBase64URL(buffer: ArrayBuffer): string {
|
||||||
|
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=+$/, '');
|
||||||
|
}
|
||||||
76
package-lock.json
generated
76
package-lock.json
generated
|
|
@ -110,7 +110,7 @@
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-ldapauth": "^3.0.1",
|
"passport-ldapauth": "^3.0.1",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"passport-simple-webauthn2": "^3.0.5",
|
"passport-simple-webauthn2": "^3.2.0",
|
||||||
"sharp": "^0.32.6",
|
"sharp": "^0.32.6",
|
||||||
"tiktoken": "^1.0.15",
|
"tiktoken": "^1.0.15",
|
||||||
"traverse": "^0.6.7",
|
"traverse": "^0.6.7",
|
||||||
|
|
@ -954,6 +954,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api/node_modules/passport-simple-webauthn2": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/passport-simple-webauthn2/-/passport-simple-webauthn2-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-Y5wwa16SmUZpgBh+xSjwng/px+ocIZZ5dil1AO2zNwPSIYGl5nuKHf0QvzHQSHxxbO9txW5+JsqAmimL2eYoBw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@simplewebauthn/server": "^13.1.1",
|
||||||
|
"base64url": "^3.0.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.7",
|
||||||
|
"passport-strategy": "^1.0.0",
|
||||||
|
"redis": "^4.7.0",
|
||||||
|
"uuid": "^11.0.5",
|
||||||
|
"winston": "^3.17.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=21"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": "^4.17.0",
|
||||||
|
"express-session": "^1.17.0",
|
||||||
|
"passport": "^0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"api/node_modules/passport-simple-webauthn2/node_modules/uuid": {
|
||||||
|
"version": "11.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
|
||||||
|
"integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/esm/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"api/node_modules/superagent": {
|
"api/node_modules/superagent": {
|
||||||
"version": "9.0.2",
|
"version": "9.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz",
|
||||||
|
|
@ -29548,43 +29585,6 @@
|
||||||
"url": "https://github.com/sponsors/jaredhanson"
|
"url": "https://github.com/sponsors/jaredhanson"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/passport-simple-webauthn2": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/passport-simple-webauthn2/-/passport-simple-webauthn2-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-FXJWXRNYUtD1At4nVCzDY0DFZ/3/VQEucHvNEA/zzwwRj11V+hAp/Z2Vc1NmQrCGGdRktzZlHt9OIZHwqim13Q==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@simplewebauthn/server": "^13.1.1",
|
|
||||||
"base64url": "^3.0.1",
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"dotenv": "^16.4.7",
|
|
||||||
"passport-strategy": "^1.0.0",
|
|
||||||
"redis": "^4.7.0",
|
|
||||||
"uuid": "^11.0.5",
|
|
||||||
"winston": "^3.17.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=21"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"express": "^4.17.0",
|
|
||||||
"express-session": "^1.17.0",
|
|
||||||
"passport": "^0.6.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/passport-simple-webauthn2/node_modules/uuid": {
|
|
||||||
"version": "11.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
|
|
||||||
"integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
|
|
||||||
"funding": [
|
|
||||||
"https://github.com/sponsors/broofa",
|
|
||||||
"https://github.com/sponsors/ctavan"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/esm/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/passport-strategy": {
|
"node_modules/passport-strategy": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue