Merge pull request #14 from ildyria/support-XCRF
[1.x] Fix: support for xcrf token
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@ docs
|
|||||||
vendor
|
vendor
|
||||||
coverage
|
coverage
|
||||||
.phpunit.result.cache
|
.phpunit.result.cache
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -61,25 +61,49 @@ class WebAuthn {
|
|||||||
* @param routes {{registerOptions: string, register: string, loginOptions: string, login: string}}
|
* @param routes {{registerOptions: string, register: string, loginOptions: string, login: string}}
|
||||||
* @param headers {{string}}
|
* @param headers {{string}}
|
||||||
* @param includeCredentials {boolean}
|
* @param includeCredentials {boolean}
|
||||||
* @param xsrfToken {string|null}
|
* @param xcsrfToken {string|null} Either a csrf token (40 chars) or xsrfToken (224 chars)
|
||||||
*/
|
*/
|
||||||
constructor(routes = {}, headers = {}, includeCredentials = false, xsrfToken = null) {
|
constructor(routes = {}, headers = {}, includeCredentials = false, xcsrfToken = null) {
|
||||||
Object.assign(this.#routes, routes);
|
Object.assign(this.#routes, routes);
|
||||||
Object.assign(this.#headers, headers);
|
Object.assign(this.#headers, headers);
|
||||||
|
|
||||||
this.#includeCredentials = includeCredentials;
|
this.#includeCredentials = includeCredentials;
|
||||||
|
|
||||||
|
let xsrfToken;
|
||||||
|
let csrfToken;
|
||||||
|
|
||||||
|
if (xcsrfToken === null) {
|
||||||
// If the developer didn't issue an XSRF token, we will find it ourselves.
|
// If the developer didn't issue an XSRF token, we will find it ourselves.
|
||||||
this.#headers["X-CSRF-TOKEN"] ??= xsrfToken ?? WebAuthn.#firstInputWithXsrfToken;
|
xsrfToken = WebAuthn.#XsrfToken;
|
||||||
|
csrfToken = WebAuthn.#firstInputWithCsrfToken;
|
||||||
|
} else{
|
||||||
|
// Check if it is a CSRF or XSRF token
|
||||||
|
if (xcsrfToken.length === 40) {
|
||||||
|
csrfToken = xcsrfToken;
|
||||||
|
} else if (xcsrfToken.length === 224) {
|
||||||
|
xsrfToken = xcsrfToken;
|
||||||
|
} else {
|
||||||
|
throw new TypeError('CSRF token or XSRF token provided does not match requirements. Must be 40 or 224 characters.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xsrfToken !== null) {
|
||||||
|
this.#headers["X-XSRF-TOKEN"] ??= xsrfToken;
|
||||||
|
} else if (csrfToken !== null) {
|
||||||
|
this.#headers["X-CSRF-TOKEN"] ??= csrfToken;
|
||||||
|
} else {
|
||||||
|
// We didn't find it, and since is required, we will bail out.
|
||||||
|
throw new TypeError('Ensure a CSRF/XSRF token is manually set, or provided in a cookie "XSRF-TOKEN" or or there is meta tag named "csrf-token".');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the XSRF token if it exists as a form input tag.
|
* Returns the CSRF token if it exists as a form input tag.
|
||||||
*
|
*
|
||||||
* @returns string
|
* @returns string
|
||||||
* @throws TypeError
|
* @throws TypeError
|
||||||
*/
|
*/
|
||||||
static get #firstInputWithXsrfToken() {
|
static get #firstInputWithCsrfToken() {
|
||||||
// First, try finding an CSRF Token in the head.
|
// First, try finding an CSRF Token in the head.
|
||||||
let token = Array.from(document.head.getElementsByTagName("meta"))
|
let token = Array.from(document.head.getElementsByTagName("meta"))
|
||||||
.find(element => element.name === "csrf-token");
|
.find(element => element.name === "csrf-token");
|
||||||
@@ -96,10 +120,33 @@ class WebAuthn {
|
|||||||
return token.value;
|
return token.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We didn't find it, and since is required, we will bail out.
|
return null;
|
||||||
throw new TypeError('Ensure a CSRF token is manually set, or there is meta tag named "csrf-token".');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value of the XSRF token if it exists in a cookie.
|
||||||
|
*
|
||||||
|
* Inspired by https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie#example_2_get_a_sample_cookie_named_test2
|
||||||
|
*
|
||||||
|
* @returns {?string}
|
||||||
|
*/
|
||||||
|
static get #XsrfToken() {
|
||||||
|
const cookie = document.cookie.split(";").find((row) => /^\s*(X-)?[XC]SRF-TOKEN\s*=/.test(row));
|
||||||
|
// We must remove all '%3D' from the end of the string.
|
||||||
|
// Background:
|
||||||
|
// The actual binary value of the CSFR value is encoded in Base64.
|
||||||
|
// If the length of original, binary value is not a multiple of 3 bytes,
|
||||||
|
// the encoding gets padded with `=` on the right; i.e. there might be
|
||||||
|
// zero, one or two `=` at the end of the encoded value.
|
||||||
|
// If the value is sent from the server to the client as part of a cookie,
|
||||||
|
// the `=` character is URL-encoded as `%3D`, because `=` is already used
|
||||||
|
// to separate a cookie key from its value.
|
||||||
|
// When we send back the value to the server as part of an AJAX request,
|
||||||
|
// Laravel expects an unpadded value.
|
||||||
|
// Hence, we must remove the `%3D`.
|
||||||
|
return cookie ? cookie.split("=")[1].trim().replaceAll("%3D", "") : null;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a fetch promise to resolve later.
|
* Returns a fetch promise to resolve later.
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user