@test2doc/playwright-passkey-gen 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +167 -0
- package/bin/generate-passkey +2 -0
- package/dist/src/generate-passkey/cli.d.ts +2 -0
- package/dist/src/generate-passkey/cli.d.ts.map +1 -0
- package/dist/src/generate-passkey/cli.js +32 -0
- package/dist/src/generate-passkey/cli.js.map +1 -0
- package/dist/src/generate-passkey/generateTestPasskey.test.d.ts +2 -0
- package/dist/src/generate-passkey/generateTestPasskey.test.d.ts.map +1 -0
- package/dist/src/generate-passkey/generateTestPasskey.test.js +143 -0
- package/dist/src/generate-passkey/generateTestPasskey.test.js.map +1 -0
- package/dist/src/generate-passkey/index.d.ts +10 -0
- package/dist/src/generate-passkey/index.d.ts.map +1 -0
- package/dist/src/generate-passkey/index.js +158 -0
- package/dist/src/generate-passkey/index.js.map +1 -0
- package/dist/src/generate-passkey/test-cli.d.ts +2 -0
- package/dist/src/generate-passkey/test-cli.d.ts.map +1 -0
- package/dist/src/generate-passkey/test-cli.js +31 -0
- package/dist/src/generate-passkey/test-cli.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/passkey-util.d.ts +19 -0
- package/dist/src/passkey-util.d.ts.map +1 -0
- package/dist/src/passkey-util.js +58 -0
- package/dist/src/passkey-util.js.map +1 -0
- package/dist/src/scripts/client.d.ts +2 -0
- package/dist/src/scripts/client.d.ts.map +1 -0
- package/dist/src/scripts/client.js +97 -0
- package/dist/src/scripts/client.js.map +1 -0
- package/dist/src/scripts/server.d.ts +2 -0
- package/dist/src/scripts/server.d.ts.map +1 -0
- package/dist/src/scripts/server.js +224 -0
- package/dist/src/scripts/server.js.map +1 -0
- package/dist/src/scripts/testpasskey.d.ts +10 -0
- package/dist/src/scripts/testpasskey.d.ts.map +1 -0
- package/dist/src/scripts/testpasskey.js +88 -0
- package/dist/src/scripts/testpasskey.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +50 -0
- package/playwright-report/index.html +85 -0
- package/playwright.config.ts +80 -0
- package/src/generate-passkey/cli.ts +33 -0
- package/src/generate-passkey/generateTestPasskey.test.ts +165 -0
- package/src/generate-passkey/index.ts +228 -0
- package/src/generate-passkey/test-cli.ts +32 -0
- package/src/scripts/client.ts +136 -0
- package/src/scripts/server.ts +301 -0
- package/src/scripts/testpasskey.ts +87 -0
- package/test-passkey.js +87 -0
- package/test-passkey.ts +87 -0
- package/test-results/.last-run.json +4 -0
- package/tests/passkey.spec.ts +29 -0
- package/tsconfig.json +52 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { createServer, type ServerResponse, type IncomingMessage } from "http"
|
|
2
|
+
import {
|
|
3
|
+
generateRegistrationOptions,
|
|
4
|
+
generateAuthenticationOptions,
|
|
5
|
+
verifyAuthenticationResponse,
|
|
6
|
+
} from "@simplewebauthn/server"
|
|
7
|
+
import { clientScript } from "./client.js"
|
|
8
|
+
import { TESTPASSKEY } from "./testpasskey.js"
|
|
9
|
+
|
|
10
|
+
interface StoredCredential {
|
|
11
|
+
id: string
|
|
12
|
+
publicKey: Uint8Array
|
|
13
|
+
counter: number
|
|
14
|
+
userId: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface StoredChallenge {
|
|
18
|
+
challenge: string
|
|
19
|
+
userId?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// In-memory storage for demo
|
|
23
|
+
const credentials = new Map<string, StoredCredential>()
|
|
24
|
+
const challenges = new Map<string, StoredChallenge>()
|
|
25
|
+
|
|
26
|
+
const rpID = "localhost"
|
|
27
|
+
const rpName = "Test App"
|
|
28
|
+
const port = 5173
|
|
29
|
+
const origin = `http://localhost:${port}`
|
|
30
|
+
|
|
31
|
+
async function parseBody(req: IncomingMessage): Promise<UnknownJson> {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
let body = ""
|
|
34
|
+
req.on("data", (chunk: string) => {
|
|
35
|
+
body += chunk
|
|
36
|
+
})
|
|
37
|
+
req.on("end", () => {
|
|
38
|
+
try {
|
|
39
|
+
resolve(JSON.parse(body))
|
|
40
|
+
} catch {
|
|
41
|
+
resolve({})
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
req.on("error", reject)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function sendJson(
|
|
49
|
+
res: ServerResponse<IncomingMessage>,
|
|
50
|
+
data: unknown,
|
|
51
|
+
status = 200,
|
|
52
|
+
) {
|
|
53
|
+
res.writeHead(status, { "Content-Type": "application/json" })
|
|
54
|
+
res.end(JSON.stringify(data))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const server = createServer(async (req, res) => {
|
|
58
|
+
// CORS headers
|
|
59
|
+
res.setHeader("Access-Control-Allow-Origin", "*")
|
|
60
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
61
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type")
|
|
62
|
+
|
|
63
|
+
if (req.method === "OPTIONS") {
|
|
64
|
+
res.writeHead(200)
|
|
65
|
+
res.end()
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const body = await parseBody(req)
|
|
70
|
+
|
|
71
|
+
// Serve HTML client
|
|
72
|
+
if (req.url === "/" && req.method === "GET") {
|
|
73
|
+
const html = `<!DOCTYPE html>
|
|
74
|
+
<html lang="en">
|
|
75
|
+
<head>
|
|
76
|
+
<meta charset="UTF-8">
|
|
77
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
78
|
+
<title>Passkey Test Client</title>
|
|
79
|
+
<style>
|
|
80
|
+
body {
|
|
81
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
82
|
+
max-width: 600px;
|
|
83
|
+
margin: 50px auto;
|
|
84
|
+
padding: 20px;
|
|
85
|
+
}
|
|
86
|
+
button {
|
|
87
|
+
padding: 10px 20px;
|
|
88
|
+
font-size: 16px;
|
|
89
|
+
cursor: pointer;
|
|
90
|
+
background: #007bff;
|
|
91
|
+
color: white;
|
|
92
|
+
border: none;
|
|
93
|
+
border-radius: 4px;
|
|
94
|
+
}
|
|
95
|
+
button:hover {
|
|
96
|
+
background: #0056b3;
|
|
97
|
+
}
|
|
98
|
+
.status {
|
|
99
|
+
margin-top: 20px;
|
|
100
|
+
padding: 10px;
|
|
101
|
+
border-radius: 4px;
|
|
102
|
+
}
|
|
103
|
+
.success {
|
|
104
|
+
background: #d4edda;
|
|
105
|
+
color: #155724;
|
|
106
|
+
border: 1px solid #c3e6cb;
|
|
107
|
+
}
|
|
108
|
+
.error {
|
|
109
|
+
background: #f8d7da;
|
|
110
|
+
color: #721c24;
|
|
111
|
+
border: 1px solid #f5c6cb;
|
|
112
|
+
}
|
|
113
|
+
.loading {
|
|
114
|
+
background: #e2e3e5;
|
|
115
|
+
color: #383d41;
|
|
116
|
+
border: 1px solid #d6d8db;
|
|
117
|
+
}
|
|
118
|
+
</style>
|
|
119
|
+
</head>
|
|
120
|
+
<body>
|
|
121
|
+
<h1>Passkey Authentication Test</h1>
|
|
122
|
+
|
|
123
|
+
<button onclick="testAuthentication()">Test Login with Passkey</button>
|
|
124
|
+
|
|
125
|
+
<div role="status" id="status"></div>
|
|
126
|
+
|
|
127
|
+
<script>
|
|
128
|
+
${clientScript}
|
|
129
|
+
</script>
|
|
130
|
+
</body>
|
|
131
|
+
</html>`
|
|
132
|
+
|
|
133
|
+
res.writeHead(200, { "Content-Type": "text/html" })
|
|
134
|
+
res.end(html)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Register start
|
|
139
|
+
if (
|
|
140
|
+
req.url === "/register/start" &&
|
|
141
|
+
req.method === "POST" &&
|
|
142
|
+
isRegisterStartBody(body)
|
|
143
|
+
) {
|
|
144
|
+
const { username, userId } = body
|
|
145
|
+
|
|
146
|
+
const options = await generateRegistrationOptions({
|
|
147
|
+
rpName,
|
|
148
|
+
rpID,
|
|
149
|
+
userName: username,
|
|
150
|
+
userID: new TextEncoder().encode(userId),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
challenges.set(userId, { challenge: options.challenge })
|
|
154
|
+
|
|
155
|
+
return sendJson(res, options)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Register finish
|
|
159
|
+
if (
|
|
160
|
+
req.url === "/register/finish" &&
|
|
161
|
+
req.method === "POST" &&
|
|
162
|
+
isRegisterFinishBody(body)
|
|
163
|
+
) {
|
|
164
|
+
const { userId, credentialId, publicKey, counter } = body
|
|
165
|
+
|
|
166
|
+
const storedChallenge = challenges.get(userId)
|
|
167
|
+
if (!storedChallenge) {
|
|
168
|
+
return sendJson(res, { error: "No challenge found" }, 400)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
credentials.set(credentialId, {
|
|
172
|
+
id: credentialId,
|
|
173
|
+
publicKey: new Uint8Array(publicKey),
|
|
174
|
+
counter,
|
|
175
|
+
userId,
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
challenges.delete(userId)
|
|
179
|
+
|
|
180
|
+
return sendJson(res, { success: true })
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Authenticate start
|
|
184
|
+
if (req.url === "/authenticate/start" && req.method === "POST") {
|
|
185
|
+
const options = await generateAuthenticationOptions({
|
|
186
|
+
rpID,
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
challenges.set("auth", { challenge: options.challenge })
|
|
190
|
+
|
|
191
|
+
return sendJson(res, options)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Authenticate finish
|
|
195
|
+
if (
|
|
196
|
+
req.url === "/authenticate/finish" &&
|
|
197
|
+
req.method === "POST" &&
|
|
198
|
+
isAuthenticateFinishBody(body)
|
|
199
|
+
) {
|
|
200
|
+
const { credentialId, clientDataJSON, authenticatorData, signature } = body
|
|
201
|
+
|
|
202
|
+
// const storedCredential = credentials.get(credentialId)
|
|
203
|
+
// if (!storedCredential) {
|
|
204
|
+
// return sendJson(res, { error: "Credential not found" }, 400)
|
|
205
|
+
// }
|
|
206
|
+
|
|
207
|
+
const storedChallenge = challenges.get("auth")
|
|
208
|
+
if (!storedChallenge) {
|
|
209
|
+
return sendJson(res, { error: "No challenge found" }, 400)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const verification = await verifyAuthenticationResponse({
|
|
214
|
+
response: {
|
|
215
|
+
id: credentialId,
|
|
216
|
+
rawId: credentialId,
|
|
217
|
+
response: {
|
|
218
|
+
clientDataJSON,
|
|
219
|
+
authenticatorData,
|
|
220
|
+
signature,
|
|
221
|
+
},
|
|
222
|
+
type: "public-key",
|
|
223
|
+
clientExtensionResults: {},
|
|
224
|
+
},
|
|
225
|
+
expectedChallenge: storedChallenge.challenge,
|
|
226
|
+
expectedOrigin: origin,
|
|
227
|
+
expectedRPID: rpID,
|
|
228
|
+
credential: {
|
|
229
|
+
id: TESTPASSKEY.credentialId,
|
|
230
|
+
publicKey: Uint8Array.from(TESTPASSKEY.publicKey),
|
|
231
|
+
counter: 0,
|
|
232
|
+
},
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
if (!verification.verified) {
|
|
236
|
+
return sendJson(res, { error: "Authentication failed" }, 400)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
challenges.delete("auth")
|
|
240
|
+
|
|
241
|
+
return sendJson(res, {
|
|
242
|
+
success: true,
|
|
243
|
+
userId: TESTPASSKEY.userId,
|
|
244
|
+
})
|
|
245
|
+
} catch (err) {
|
|
246
|
+
return sendJson(res, { error: String(err) }, 400)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
sendJson(res, { error: "Not found" }, 404)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
server.listen(port, () => {
|
|
254
|
+
console.log(`Server running on ${origin}`)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
// Type guards
|
|
258
|
+
type UnknownJson = Record<string, unknown>
|
|
259
|
+
function isRegisterStartBody(
|
|
260
|
+
body: UnknownJson,
|
|
261
|
+
): body is { username: string; userId: string } {
|
|
262
|
+
return (
|
|
263
|
+
typeof body === "object" &&
|
|
264
|
+
body !== null &&
|
|
265
|
+
typeof body["username"] === "string" &&
|
|
266
|
+
typeof body["userId"] === "string"
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function isRegisterFinishBody(body: UnknownJson): body is {
|
|
271
|
+
userId: string
|
|
272
|
+
credentialId: string
|
|
273
|
+
publicKey: number[]
|
|
274
|
+
counter: number
|
|
275
|
+
} {
|
|
276
|
+
return (
|
|
277
|
+
typeof body === "object" &&
|
|
278
|
+
body !== null &&
|
|
279
|
+
typeof body["userId"] === "string" &&
|
|
280
|
+
typeof body["credentialId"] === "string" &&
|
|
281
|
+
Array.isArray(body["publicKey"]) &&
|
|
282
|
+
typeof body["counter"] === "number"
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function isAuthenticateFinishBody(body: UnknownJson): body is {
|
|
287
|
+
credentialId: string
|
|
288
|
+
clientDataJSON: string
|
|
289
|
+
authenticatorData: string
|
|
290
|
+
signature: string
|
|
291
|
+
signCount: number
|
|
292
|
+
} {
|
|
293
|
+
return (
|
|
294
|
+
typeof body === "object" &&
|
|
295
|
+
body !== null &&
|
|
296
|
+
typeof body["credentialId"] === "string" &&
|
|
297
|
+
typeof body["clientDataJSON"] === "string" &&
|
|
298
|
+
typeof body["authenticatorData"] === "string" &&
|
|
299
|
+
typeof body["signature"] === "string"
|
|
300
|
+
)
|
|
301
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const TESTPASSKEY = {
|
|
2
|
+
"username": "testuser",
|
|
3
|
+
"userId": "5ba2106d-875d-46e9-9230-72df17b5c3c3",
|
|
4
|
+
"credentialId": "jdMitWaOVL7f4YrOojmOvMd8rPsrSWHMbYkAW3w1pB8=",
|
|
5
|
+
"publicKey": [
|
|
6
|
+
165,
|
|
7
|
+
1,
|
|
8
|
+
2,
|
|
9
|
+
3,
|
|
10
|
+
38,
|
|
11
|
+
32,
|
|
12
|
+
1,
|
|
13
|
+
33,
|
|
14
|
+
88,
|
|
15
|
+
32,
|
|
16
|
+
29,
|
|
17
|
+
141,
|
|
18
|
+
100,
|
|
19
|
+
24,
|
|
20
|
+
220,
|
|
21
|
+
131,
|
|
22
|
+
163,
|
|
23
|
+
43,
|
|
24
|
+
167,
|
|
25
|
+
203,
|
|
26
|
+
187,
|
|
27
|
+
213,
|
|
28
|
+
134,
|
|
29
|
+
200,
|
|
30
|
+
151,
|
|
31
|
+
38,
|
|
32
|
+
98,
|
|
33
|
+
107,
|
|
34
|
+
39,
|
|
35
|
+
191,
|
|
36
|
+
21,
|
|
37
|
+
16,
|
|
38
|
+
139,
|
|
39
|
+
92,
|
|
40
|
+
205,
|
|
41
|
+
177,
|
|
42
|
+
222,
|
|
43
|
+
23,
|
|
44
|
+
7,
|
|
45
|
+
193,
|
|
46
|
+
78,
|
|
47
|
+
168,
|
|
48
|
+
34,
|
|
49
|
+
88,
|
|
50
|
+
32,
|
|
51
|
+
234,
|
|
52
|
+
99,
|
|
53
|
+
242,
|
|
54
|
+
234,
|
|
55
|
+
56,
|
|
56
|
+
49,
|
|
57
|
+
232,
|
|
58
|
+
146,
|
|
59
|
+
39,
|
|
60
|
+
156,
|
|
61
|
+
161,
|
|
62
|
+
184,
|
|
63
|
+
240,
|
|
64
|
+
192,
|
|
65
|
+
48,
|
|
66
|
+
63,
|
|
67
|
+
252,
|
|
68
|
+
203,
|
|
69
|
+
93,
|
|
70
|
+
253,
|
|
71
|
+
115,
|
|
72
|
+
25,
|
|
73
|
+
48,
|
|
74
|
+
71,
|
|
75
|
+
76,
|
|
76
|
+
11,
|
|
77
|
+
129,
|
|
78
|
+
16,
|
|
79
|
+
158,
|
|
80
|
+
74,
|
|
81
|
+
136,
|
|
82
|
+
201
|
|
83
|
+
],
|
|
84
|
+
"privateKey": "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPyrpgV3J+gBgsuPq3waX31oWxvvp+RDMtsXzYiaguhuhRANCAAQdjWQY3IOjK6fLu9WGyJcmYmsnvxUQi1zNsd4XB8FOqOpj8uo4MeiSJ5yhuPDAMD/8y139cxkwR0wLgRCeSojJ",
|
|
85
|
+
"credentialDbId": "jdMitWaOVL7f4YrOojmOvMd8rPsrSWHMbYkAW3w1pB8",
|
|
86
|
+
"signCount": 1
|
|
87
|
+
}
|
package/test-passkey.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const TESTPASSKEY = {
|
|
2
|
+
"username": "testuser",
|
|
3
|
+
"userId": "c5348c9f-9c67-471e-b129-9fdc478d37c5",
|
|
4
|
+
"credentialId": "e+700BnFW4TyYj1lQ6na6vVXYNNGyZCKgrg80dznnrU=",
|
|
5
|
+
"publicKey": [
|
|
6
|
+
165,
|
|
7
|
+
1,
|
|
8
|
+
2,
|
|
9
|
+
3,
|
|
10
|
+
38,
|
|
11
|
+
32,
|
|
12
|
+
1,
|
|
13
|
+
33,
|
|
14
|
+
88,
|
|
15
|
+
32,
|
|
16
|
+
11,
|
|
17
|
+
229,
|
|
18
|
+
191,
|
|
19
|
+
89,
|
|
20
|
+
168,
|
|
21
|
+
16,
|
|
22
|
+
62,
|
|
23
|
+
193,
|
|
24
|
+
63,
|
|
25
|
+
58,
|
|
26
|
+
8,
|
|
27
|
+
204,
|
|
28
|
+
115,
|
|
29
|
+
175,
|
|
30
|
+
44,
|
|
31
|
+
47,
|
|
32
|
+
255,
|
|
33
|
+
171,
|
|
34
|
+
41,
|
|
35
|
+
246,
|
|
36
|
+
125,
|
|
37
|
+
125,
|
|
38
|
+
74,
|
|
39
|
+
244,
|
|
40
|
+
149,
|
|
41
|
+
228,
|
|
42
|
+
201,
|
|
43
|
+
104,
|
|
44
|
+
183,
|
|
45
|
+
132,
|
|
46
|
+
115,
|
|
47
|
+
43,
|
|
48
|
+
34,
|
|
49
|
+
88,
|
|
50
|
+
32,
|
|
51
|
+
69,
|
|
52
|
+
201,
|
|
53
|
+
239,
|
|
54
|
+
184,
|
|
55
|
+
155,
|
|
56
|
+
115,
|
|
57
|
+
219,
|
|
58
|
+
46,
|
|
59
|
+
159,
|
|
60
|
+
194,
|
|
61
|
+
32,
|
|
62
|
+
122,
|
|
63
|
+
149,
|
|
64
|
+
235,
|
|
65
|
+
189,
|
|
66
|
+
219,
|
|
67
|
+
5,
|
|
68
|
+
76,
|
|
69
|
+
123,
|
|
70
|
+
13,
|
|
71
|
+
76,
|
|
72
|
+
120,
|
|
73
|
+
53,
|
|
74
|
+
222,
|
|
75
|
+
15,
|
|
76
|
+
236,
|
|
77
|
+
5,
|
|
78
|
+
44,
|
|
79
|
+
151,
|
|
80
|
+
93,
|
|
81
|
+
237,
|
|
82
|
+
31
|
|
83
|
+
],
|
|
84
|
+
"privateKey": "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgLc8nJ8DKp0mr2f7dNc0m1s0f+dpkRw5WUh8ceELq8xqhRANCAAQL5b9ZqBA+wT86CMxzrywv/6sp9n19SvSV5Mlot4RzK0XJ77ibc9sun8IgepXrvdsFTHsNTHg13g/sBSyXXe0f",
|
|
85
|
+
"credentialDbId": "e-700BnFW4TyYj1lQ6na6vVXYNNGyZCKgrg80dznnrU",
|
|
86
|
+
"signCount": 1
|
|
87
|
+
}
|
package/test-passkey.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const TESTPASSKEY = {
|
|
2
|
+
"username": "testuser",
|
|
3
|
+
"userId": "6273c61b-2947-4b6d-b713-ca0a366f88ae",
|
|
4
|
+
"credentialId": "9no4Mc2LnqVXpLtBeZlQgidBnnAC2GYrDdho7CRXZSg=",
|
|
5
|
+
"publicKey": [
|
|
6
|
+
165,
|
|
7
|
+
1,
|
|
8
|
+
2,
|
|
9
|
+
3,
|
|
10
|
+
38,
|
|
11
|
+
32,
|
|
12
|
+
1,
|
|
13
|
+
33,
|
|
14
|
+
88,
|
|
15
|
+
32,
|
|
16
|
+
159,
|
|
17
|
+
234,
|
|
18
|
+
226,
|
|
19
|
+
78,
|
|
20
|
+
12,
|
|
21
|
+
226,
|
|
22
|
+
49,
|
|
23
|
+
223,
|
|
24
|
+
194,
|
|
25
|
+
88,
|
|
26
|
+
59,
|
|
27
|
+
157,
|
|
28
|
+
194,
|
|
29
|
+
204,
|
|
30
|
+
149,
|
|
31
|
+
9,
|
|
32
|
+
141,
|
|
33
|
+
183,
|
|
34
|
+
47,
|
|
35
|
+
151,
|
|
36
|
+
203,
|
|
37
|
+
187,
|
|
38
|
+
33,
|
|
39
|
+
227,
|
|
40
|
+
215,
|
|
41
|
+
208,
|
|
42
|
+
88,
|
|
43
|
+
100,
|
|
44
|
+
221,
|
|
45
|
+
187,
|
|
46
|
+
9,
|
|
47
|
+
185,
|
|
48
|
+
34,
|
|
49
|
+
88,
|
|
50
|
+
32,
|
|
51
|
+
112,
|
|
52
|
+
147,
|
|
53
|
+
130,
|
|
54
|
+
189,
|
|
55
|
+
166,
|
|
56
|
+
43,
|
|
57
|
+
126,
|
|
58
|
+
124,
|
|
59
|
+
232,
|
|
60
|
+
48,
|
|
61
|
+
242,
|
|
62
|
+
148,
|
|
63
|
+
47,
|
|
64
|
+
144,
|
|
65
|
+
179,
|
|
66
|
+
100,
|
|
67
|
+
239,
|
|
68
|
+
118,
|
|
69
|
+
136,
|
|
70
|
+
123,
|
|
71
|
+
28,
|
|
72
|
+
216,
|
|
73
|
+
154,
|
|
74
|
+
5,
|
|
75
|
+
60,
|
|
76
|
+
173,
|
|
77
|
+
137,
|
|
78
|
+
15,
|
|
79
|
+
54,
|
|
80
|
+
40,
|
|
81
|
+
160,
|
|
82
|
+
18
|
|
83
|
+
],
|
|
84
|
+
"privateKey": "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfm5Shc4ds68wEoSJX8vpfuQBUsT96Sp0Z5S1B9xAwzWhRANCAASf6uJODOIx38JYO53CzJUJjbcvl8u7IePX0Fhk3bsJuXCTgr2mK3586DDylC+Qs2Tvdoh7HNiaBTytiQ82KKAS",
|
|
85
|
+
"credentialDbId": "9no4Mc2LnqVXpLtBeZlQgidBnnAC2GYrDdho7CRXZSg",
|
|
86
|
+
"signCount": 1
|
|
87
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { test, expect } from "@playwright/test"
|
|
2
|
+
import { TESTPASSKEY } from "../src/scripts/testpasskey.js"
|
|
3
|
+
import {
|
|
4
|
+
enableVirtualAuthenticator,
|
|
5
|
+
addPasskeyCredential,
|
|
6
|
+
simulateSuccessfulPasskeyInput,
|
|
7
|
+
} from "@test2doc/playwright-passkey"
|
|
8
|
+
|
|
9
|
+
test.describe("Passkey Authentication", () => {
|
|
10
|
+
test("should authenticate using passkey", async ({ page }) => {
|
|
11
|
+
await page.goto("http://localhost:5173/")
|
|
12
|
+
const passkeyAuthenticator = await enableVirtualAuthenticator(page)
|
|
13
|
+
await addPasskeyCredential(passkeyAuthenticator, TESTPASSKEY)
|
|
14
|
+
|
|
15
|
+
await expect(page.getByRole("status")).toHaveText("")
|
|
16
|
+
|
|
17
|
+
// Click the "Test Login with Passkey" button
|
|
18
|
+
await simulateSuccessfulPasskeyInput(passkeyAuthenticator, async () => {
|
|
19
|
+
await page
|
|
20
|
+
.getByRole("button", { name: "Test Login with Passkey" })
|
|
21
|
+
.click()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
// Wait for the success message
|
|
25
|
+
await expect(page.getByRole("status")).toContainText(
|
|
26
|
+
`Authentication successful! User ID: ${TESTPASSKEY.userId}`,
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
/* Project Options */
|
|
4
|
+
"target": "es2024" /* Specify ECMAScript target version for emitted JavaScript. */,
|
|
5
|
+
"lib": [
|
|
6
|
+
"esnext",
|
|
7
|
+
"DOM"
|
|
8
|
+
] /* Specify a list of library files to be included in the compilation. */,
|
|
9
|
+
"types": ["node"],
|
|
10
|
+
"sourceMap": true /* Generates corresponding .map file. */,
|
|
11
|
+
|
|
12
|
+
/* Strictness & Type Checking Options */
|
|
13
|
+
"strict": true /* Enable all strict type-checking options. Highly recommended. */,
|
|
14
|
+
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
|
15
|
+
"strictNullChecks": true /* Enable strict null checks. */,
|
|
16
|
+
"strictFunctionTypes": true /* Enable strict checking of function types. */,
|
|
17
|
+
"strictBindCallApply": true /* Enable strict 'bind', 'call', and 'apply' methods on functions. */,
|
|
18
|
+
"strictPropertyInitialization": true /* Ensure non-null class properties are initialized. */,
|
|
19
|
+
"noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */,
|
|
20
|
+
"useUnknownInCatchVariables": true /* Type catch clause variables as 'unknown' instead of 'any'. */,
|
|
21
|
+
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
|
|
22
|
+
"noUnusedLocals": true /* Report errors on unused locals. */,
|
|
23
|
+
"noUnusedParameters": true /* Report errors on unused parameters. */,
|
|
24
|
+
"exactOptionalPropertyTypes": true /* Report errors for extra properties in object literals. */,
|
|
25
|
+
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
|
|
26
|
+
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
|
|
27
|
+
"noUncheckedIndexedAccess": true /* Include 'undefined' in the type of indexed access. */,
|
|
28
|
+
"noPropertyAccessFromIndexSignature": true /* Disallow dot-accessing properties with string index signatures. */,
|
|
29
|
+
"assumeChangesOnlyAffectDirectDependencies": false /* More accurate incremental builds. */,
|
|
30
|
+
|
|
31
|
+
/* Module Resolution Options */
|
|
32
|
+
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
|
33
|
+
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
|
|
34
|
+
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
|
35
|
+
|
|
36
|
+
/* Other Options */
|
|
37
|
+
"skipLibCheck": true /* Skip type checking all .d.ts files. Improves compilation speed. */,
|
|
38
|
+
"pretty": true /* Enable color and context in error messages. */,
|
|
39
|
+
"newLine": "lf" /* Use LF for line endings. */,
|
|
40
|
+
"module": "nodenext", // Emit ES Modules (for Node.js's dual module system)
|
|
41
|
+
"moduleResolution": "nodenext",
|
|
42
|
+
"outDir": "./dist", // Output to an 'esm' subdirectory
|
|
43
|
+
"declaration": true, // Still generate declarations
|
|
44
|
+
"declarationMap": true, // Still generate declaration maps
|
|
45
|
+
"composite": true // Keep composite if you use project references
|
|
46
|
+
},
|
|
47
|
+
"include": ["src/**/*"],
|
|
48
|
+
"exclude": [
|
|
49
|
+
"node_modules",
|
|
50
|
+
"dist",
|
|
51
|
+
]
|
|
52
|
+
}
|