|
| 1 | +/* |
| 2 | + * Copyright (c) Microsoft Corporation. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package com.microsoft.playwright; |
| 18 | + |
| 19 | +import com.microsoft.playwright.options.*; |
| 20 | +import java.util.*; |
| 21 | + |
| 22 | +/** |
| 23 | + * {@code Credentials} is a virtual WebAuthn authenticator scoped to a {@code BrowserContext}. It lets tests register |
| 24 | + * passkeys and answer {@code navigator.credentials.create()} / {@code navigator.credentials.get()} ceremonies in the page, |
| 25 | + * without a real authenticator or hardware security key. |
| 26 | + * |
| 27 | + * <p> There are two common ways to use it: |
| 28 | + * |
| 29 | + * <p> <strong>Usage: seed a known credential</strong> |
| 30 | + * <pre>{@code |
| 31 | + * BrowserContext context = browser.newContext(); |
| 32 | + * |
| 33 | + * // A passkey your backend already provisioned for a test user. |
| 34 | + * context.credentials().create("example.com", new Credentials.CreateOptions() |
| 35 | + * .setId(knownCredentialId) // base64url |
| 36 | + * .setUserHandle(knownUserHandle) // base64url |
| 37 | + * .setPrivateKey(knownPrivateKey) // base64url PKCS#8 (DER) |
| 38 | + * .setPublicKey(knownPublicKey)); // base64url SPKI (DER) |
| 39 | + * context.credentials().install(); |
| 40 | + * |
| 41 | + * Page page = context.newPage(); |
| 42 | + * page.navigate("https://example.com/login"); |
| 43 | + * // The page's navigator.credentials.get() is answered with the seeded passkey. |
| 44 | + * }</pre> |
| 45 | + * |
| 46 | + * <p> <strong>Usage: capture a passkey, then reuse it</strong> |
| 47 | + * <pre>{@code |
| 48 | + * // setup test: let the app register a passkey, then save it. |
| 49 | + * BrowserContext context = browser.newContext(); |
| 50 | + * context.credentials().install(); |
| 51 | + * |
| 52 | + * Page page = context.newPage(); |
| 53 | + * page.navigate("https://example.com/register"); |
| 54 | + * page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Create a passkey")).click(); |
| 55 | + * |
| 56 | + * // Read back the passkey the page registered — it includes the private key. |
| 57 | + * VirtualCredential credential = context.credentials().get( |
| 58 | + * new Credentials.GetOptions().setRpId("example.com")).get(0); |
| 59 | + * Files.writeString(Paths.get("playwright/.auth/passkey.json"), new Gson().toJson(credential)); |
| 60 | + * }</pre> |
| 61 | + * <pre>{@code |
| 62 | + * // later test: seed the captured passkey so the app starts already enrolled. |
| 63 | + * VirtualCredential credential = new Gson().fromJson( |
| 64 | + * Files.readString(Paths.get("playwright/.auth/passkey.json")), VirtualCredential.class); |
| 65 | + * BrowserContext context = browser.newContext(); |
| 66 | + * context.credentials().create(credential.rpId, new Credentials.CreateOptions() |
| 67 | + * .setId(credential.id) |
| 68 | + * .setUserHandle(credential.userHandle) |
| 69 | + * .setPrivateKey(credential.privateKey) |
| 70 | + * .setPublicKey(credential.publicKey)); |
| 71 | + * context.credentials().install(); |
| 72 | + * |
| 73 | + * Page page = context.newPage(); |
| 74 | + * page.navigate("https://example.com/login"); |
| 75 | + * // navigator.credentials.get() resolves the captured passkey — already signed in. |
| 76 | + * }</pre> |
| 77 | + * |
| 78 | + * <p> <strong>Defaults</strong> |
| 79 | + */ |
| 80 | +public interface Credentials { |
| 81 | + class CreateOptions { |
| 82 | + /** |
| 83 | + * Base64url-encoded credential id. Auto-generated if omitted. |
| 84 | + */ |
| 85 | + public String id; |
| 86 | + /** |
| 87 | + * Base64url-encoded PKCS#8 (DER) private key. Auto-generated if omitted. |
| 88 | + */ |
| 89 | + public String privateKey; |
| 90 | + /** |
| 91 | + * Base64url-encoded SPKI (DER) public key. Auto-generated if omitted. |
| 92 | + */ |
| 93 | + public String publicKey; |
| 94 | + /** |
| 95 | + * Base64url-encoded user handle. Auto-generated if omitted. |
| 96 | + */ |
| 97 | + public String userHandle; |
| 98 | + |
| 99 | + /** |
| 100 | + * Base64url-encoded credential id. Auto-generated if omitted. |
| 101 | + */ |
| 102 | + public CreateOptions setId(String id) { |
| 103 | + this.id = id; |
| 104 | + return this; |
| 105 | + } |
| 106 | + /** |
| 107 | + * Base64url-encoded PKCS#8 (DER) private key. Auto-generated if omitted. |
| 108 | + */ |
| 109 | + public CreateOptions setPrivateKey(String privateKey) { |
| 110 | + this.privateKey = privateKey; |
| 111 | + return this; |
| 112 | + } |
| 113 | + /** |
| 114 | + * Base64url-encoded SPKI (DER) public key. Auto-generated if omitted. |
| 115 | + */ |
| 116 | + public CreateOptions setPublicKey(String publicKey) { |
| 117 | + this.publicKey = publicKey; |
| 118 | + return this; |
| 119 | + } |
| 120 | + /** |
| 121 | + * Base64url-encoded user handle. Auto-generated if omitted. |
| 122 | + */ |
| 123 | + public CreateOptions setUserHandle(String userHandle) { |
| 124 | + this.userHandle = userHandle; |
| 125 | + return this; |
| 126 | + } |
| 127 | + } |
| 128 | + class GetOptions { |
| 129 | + /** |
| 130 | + * Only return the credential with this base64url-encoded id. |
| 131 | + */ |
| 132 | + public String id; |
| 133 | + /** |
| 134 | + * Only return credentials for this relying party id. |
| 135 | + */ |
| 136 | + public String rpId; |
| 137 | + |
| 138 | + /** |
| 139 | + * Only return the credential with this base64url-encoded id. |
| 140 | + */ |
| 141 | + public GetOptions setId(String id) { |
| 142 | + this.id = id; |
| 143 | + return this; |
| 144 | + } |
| 145 | + /** |
| 146 | + * Only return credentials for this relying party id. |
| 147 | + */ |
| 148 | + public GetOptions setRpId(String rpId) { |
| 149 | + this.rpId = rpId; |
| 150 | + return this; |
| 151 | + } |
| 152 | + } |
| 153 | + /** |
| 154 | + * Installs the virtual WebAuthn authenticator into the context, overriding {@code navigator.credentials.create()} and |
| 155 | + * {@code navigator.credentials.get()} in all current and future pages. Call this before the page first touches {@code |
| 156 | + * navigator.credentials}. |
| 157 | + * |
| 158 | + * <p> Required: until {@link com.microsoft.playwright.Credentials#install Credentials.install()} is called, no interception is |
| 159 | + * in place and the page sees the platform's native (or absent) WebAuthn behaviour. Seeding credentials with {@link |
| 160 | + * com.microsoft.playwright.Credentials#create Credentials.create()} without installing populates the authenticator, but |
| 161 | + * the page will never see those credentials. |
| 162 | + * |
| 163 | + * @since v1.61 |
| 164 | + */ |
| 165 | + void install(); |
| 166 | + /** |
| 167 | + * Seeds a virtual WebAuthn credential and returns it. |
| 168 | + * |
| 169 | + * <p> With only {@code rpId}, generates a fresh **ECDSA P-256** keypair, credential id and user handle. The seeded credential |
| 170 | + * is discoverable (resident), so the page can resolve it from both username-then-passkey and usernameless passkey flows. |
| 171 | + * The returned object carries the private and public keys, so it can be persisted to disk and re-seeded in a later test. |
| 172 | + * |
| 173 | + * <p> To **import a known credential**, supply all four of {@code id}, {@code userHandle}, {@code privateKey} and {@code |
| 174 | + * publicKey} together. |
| 175 | + * |
| 176 | + * <p> Call {@link com.microsoft.playwright.Credentials#install Credentials.install()} before navigating to a page that uses |
| 177 | + * WebAuthn. |
| 178 | + * |
| 179 | + * @param rpId Relying party id (typically the site's effective domain). |
| 180 | + * @since v1.61 |
| 181 | + */ |
| 182 | + default VirtualCredential create(String rpId) { |
| 183 | + return create(rpId, null); |
| 184 | + } |
| 185 | + /** |
| 186 | + * Seeds a virtual WebAuthn credential and returns it. |
| 187 | + * |
| 188 | + * <p> With only {@code rpId}, generates a fresh **ECDSA P-256** keypair, credential id and user handle. The seeded credential |
| 189 | + * is discoverable (resident), so the page can resolve it from both username-then-passkey and usernameless passkey flows. |
| 190 | + * The returned object carries the private and public keys, so it can be persisted to disk and re-seeded in a later test. |
| 191 | + * |
| 192 | + * <p> To **import a known credential**, supply all four of {@code id}, {@code userHandle}, {@code privateKey} and {@code |
| 193 | + * publicKey} together. |
| 194 | + * |
| 195 | + * <p> Call {@link com.microsoft.playwright.Credentials#install Credentials.install()} before navigating to a page that uses |
| 196 | + * WebAuthn. |
| 197 | + * |
| 198 | + * @param rpId Relying party id (typically the site's effective domain). |
| 199 | + * @since v1.61 |
| 200 | + */ |
| 201 | + VirtualCredential create(String rpId, CreateOptions options); |
| 202 | + /** |
| 203 | + * Removes a credential from the authenticator by its id. Works for any credential currently held — both those seeded with |
| 204 | + * {@link com.microsoft.playwright.Credentials#create Credentials.create()} and those the page registered itself by calling |
| 205 | + * {@code navigator.credentials.create()}. |
| 206 | + * |
| 207 | + * @param id Base64url-encoded credential id. |
| 208 | + * @since v1.61 |
| 209 | + */ |
| 210 | + void delete(String id); |
| 211 | + /** |
| 212 | + * Returns every credential currently held by the authenticator, optionally filtered by {@code rpId} or {@code id}. This |
| 213 | + * includes both credentials seeded with {@link com.microsoft.playwright.Credentials#create Credentials.create()} and |
| 214 | + * credentials the page registered itself by calling {@code navigator.credentials.create()}. |
| 215 | + * |
| 216 | + * <p> Each returned credential includes its private and public keys, so a passkey the app just registered can be saved and |
| 217 | + * re-seeded into a later test with {@link com.microsoft.playwright.Credentials#create Credentials.create()} — see the |
| 218 | + * second example in the class overview. |
| 219 | + * |
| 220 | + * @since v1.61 |
| 221 | + */ |
| 222 | + default List<VirtualCredential> get() { |
| 223 | + return get(null); |
| 224 | + } |
| 225 | + /** |
| 226 | + * Returns every credential currently held by the authenticator, optionally filtered by {@code rpId} or {@code id}. This |
| 227 | + * includes both credentials seeded with {@link com.microsoft.playwright.Credentials#create Credentials.create()} and |
| 228 | + * credentials the page registered itself by calling {@code navigator.credentials.create()}. |
| 229 | + * |
| 230 | + * <p> Each returned credential includes its private and public keys, so a passkey the app just registered can be saved and |
| 231 | + * re-seeded into a later test with {@link com.microsoft.playwright.Credentials#create Credentials.create()} — see the |
| 232 | + * second example in the class overview. |
| 233 | + * |
| 234 | + * @since v1.61 |
| 235 | + */ |
| 236 | + List<VirtualCredential> get(GetOptions options); |
| 237 | +} |
| 238 | + |
0 commit comments