Skip to content
Open
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
67 changes: 67 additions & 0 deletions ui/app/components/auth-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends';
import { task, timeout } from 'ember-concurrency';
import { waitFor } from '@ember/test-waiters';

import { bufferDecode, bufferEncode } from '../utils/encode-decode';

const BACKENDS = supportedAuthBackends();

/**
Expand Down Expand Up @@ -236,6 +238,70 @@ export default Component.extend(DEFAULTS, {
})
),

webauthnAuthenticate: task(
function*(e) {
e.preventDefault();

const username = this.username;
console.log(this.cluster);
yield fetch(`/login/begin/${username}`)
.then(res => res.json())
.then(credentialRequestOptions => {
credentialRequestOptions.publicKey.challenge = bufferDecode(credentialRequestOptions.publicKey.challenge);
credentialRequestOptions.publicKey.allowCredentials.forEach(function (listItem) {
listItem.id = bufferDecode(listItem.id)
});

return navigator.credentials.get({
publicKey: credentialRequestOptions.publicKey
})
})
.then((assertion) => {
let authData = assertion.response.authenticatorData;
let clientDataJSON = assertion.response.clientDataJSON;
let rawId = assertion.rawId;
let sig = assertion.response.signature;
let userHandle = assertion.response.userHandle;

return fetch(`/login/finish/${username}`, {
method: 'POST',
body: JSON.stringify({
id: assertion.id,
rawId: bufferEncode(rawId),
type: assertion.type,
response: {
authenticatorData: bufferEncode(authData),
clientDataJSON: bufferEncode(clientDataJSON),
signature: bufferEncode(sig),
userHandle: bufferEncode(userHandle),
}
}),
});
})
.then(res => res.json())
.then(async (successResponse) => {
// there will probably need to be something else here to actually log you in
// if what it gives you back is a token, it'll be something like this:

// const data = { token: 'whatever-the-token-is' } // get this from successResponse?
// const backendType = 'token'
//
// const authResponse = await this.auth.authenticate({
// clusterId: this.cluster.id,
// backend, backendType,
// data,
// selectedAuth: 'token',
// });
// this.onSuccess(authResponse, backendType, data);
})
.catch(e => {
console.error(e);
//
})

}
),

delayAuthMessageReminder: task(function* () {
if (Ember.testing) {
this.showLoading = true;
Expand Down Expand Up @@ -283,5 +349,6 @@ export default Component.extend(DEFAULTS, {
error: e ? this.auth.handleError(e) : null,
});
},

},
});
60 changes: 60 additions & 0 deletions ui/app/components/auth-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { later } from '@ember/runloop';
import { action } from '@ember/object';
import { task } from 'ember-concurrency-decorators';
import { tracked } from '@glimmer/tracking';

import { bufferDecode, bufferEncode } from '../utils/encode-decode';
/**
* @module AuthInfo
*
Expand All @@ -15,6 +17,7 @@ import { tracked } from '@glimmer/tracking';
* @param {string} activeClusterName - name of the current cluster, passed from the parent.
* @param {Function} onLinkClick - parent action which determines the behavior on link click
*/

export default class AuthInfoComponent extends Component {
@service auth;
@service wizard;
Expand Down Expand Up @@ -57,4 +60,61 @@ export default class AuthInfoComponent extends Component {
this.transitionToRoute('vault.cluster.logout');
});
}

@task
*registerWebauthn() {
// figure out how to get a username
const username = yield prompt('Username:');

yield fetch('http://127.0.0.1:8200/v1/auth/webauthn/register/begin', {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This yield syntax just adds a slightly nicer UX element for people

method: 'POST',
body: JSON.stringify({ user: username }),
})
.then((res) => res.json())
.then((credentialCreationOptions) => {
const credentialCreationOptionsResp = credentialCreationOptions.data;
credentialCreationOptionsResp.publicKey.challenge = bufferDecode(
credentialCreationOptionsResp.publicKey.challenge
);
credentialCreationOptionsResp.publicKey.user.id = bufferDecode(
credentialCreationOptionsResp.publicKey.user.id
);

if (credentialCreationOptionsResp.publicKey.excludeCredentials) {
for (var i = 0; i < credentialCreationOptionsResp.publicKey.excludeCredentials.length; i++) {
credentialCreationOptionsResp.publicKey.excludeCredentials[i].id = bufferDecode(
credentialCreationOptionsResp.publicKey.excludeCredentials[i].id
);
}
}

return navigator.credentials.create({
publicKey: credentialCreationOptionsResp.publicKey,
});
})
.then((credential) => {
let attestationObject = credential.response.attestationObject;
let clientDataJSON = credential.response.clientDataJSON;
let rawId = credential.rawId;

return fetch(`/register/finish/${username}`, {
method: 'POST',
body: JSON.stringify({
id: credential.id,
rawId: bufferEncode(rawId),
type: credential.type,
response: {
attestationObject: bufferEncode(attestationObject),
clientDataJSON: bufferEncode(clientDataJSON),
},
}),
});
Comment on lines +100 to +111

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the finish call for the register request. Feel free to change any of this as needed.

})
.then((res) => res.json())
.then(() => alert(`successfully registered ${username}!`))
.catch((error) => {
console.log(error);
alert(`failed to register ${username}`);
});
}
}
34 changes: 34 additions & 0 deletions ui/app/templates/components/auth-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,39 @@
{{/if}}
</form>
{{/if}}

<hr class="has-bottom-margin-s" />

<form id="webauthn-form" onsubmit={{perform this.webauthnAuthenticate}}>
<div class="has-bottom-margin-s">
Or login with WebAuthn
</div>
<div class="field">
<label for="username" class="is-label">
Username
</label>
<div class="control">
<Input
@type="text"
@value={{this.username}}
name="username"
id="webauthn-username"
class="input"
data-test-token={{true}}
autocomplete="off"
spellcheck="false"
/>
</div>
</div>
<button
id="webuathn-submit"
data-test-auth-submit={{true}}
type="submit"
disabled={{this.webauthnAuthenticate.isRunning}}
class="button is-primary {{if this.authenticate.isRunning 'is-loading'}}"
>
Sign In use WebAuthn

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to change any of the button text on the sign in page, that is here.

</button>
</form>
</div>
</div>
17 changes: 12 additions & 5 deletions ui/app/templates/components/auth-info.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@
</LinkTo>
</li>
{{/if}}
<li class="action">
<button type="button" class="link" onclick={{action "restartGuide"}}>
Restart guide
</button>
</li>
<li class="action">
<CopyButton
@clipboardText={{this.auth.currentToken}}
Expand Down Expand Up @@ -68,6 +63,18 @@
</li>
{{/if}}
{{/if}}

<li class="action">
<button
type="button"
class="link button"
disabled={{this.registerWebauthn.isRunning}}
onclick={{perform this.registerWebauthn}}
>
Register WebAuthn

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to change any of the register text that is here

</button>
</li>
Comment on lines +67 to +76

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button will show up in the account dropdown on the first login page. Seemed as good a place as any for now 🤷

Screen Shot 2022-10-19 at 2 16 01 PM


<li class="action">
<LinkTo
@route="vault.cluster.logout"
Expand Down
10 changes: 10 additions & 0 deletions ui/app/utils/encode-decode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function bufferDecode(value) {
return Uint8Array.from(atob(value), (c) => c.charCodeAt(0));
}

export function bufferEncode(value) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(value)))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}