Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ui] Mask token secret when logged in #19529

Merged
merged 4 commits into from
Dec 20, 2023
Merged
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
3 changes: 3 additions & 0 deletions .changelog/19529.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: Hide token secret upon successful login
```
5 changes: 5 additions & 0 deletions ui/app/styles/components/authorization.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
margin-bottom: 1.5rem;
}

.token-details {
display: grid;
gap: 1rem;
}

.or-divider {
display: block;
width: 100%;
Expand Down
283 changes: 143 additions & 140 deletions ui/app/templates/settings/tokens.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -8,167 +8,168 @@
{{#if this.isValidatingToken}}
<LoadingSpinner />
{{else}}
<h1 class="title">
{{#if this.tokenRecord}}
Profile
{{else}}
Sign In

<Hds::PageHeader as |PH|>
<PH.Title>
{{#if this.tokenRecord}}
Profile
{{else}}
Sign In
{{/if}}
</PH.Title>
<PH.Actions>
{{#if this.shouldShowPolicies}}
{{#unless this.tokenRecord.isExpired}}
<Hds::Button
data-test-token-clear
@size="medium"
@text="Sign Out"
@color="critical"
{{on "click" this.clearTokenProperties}}
/>

{{/unless}}
{{/if}}
</h1>
</PH.Actions>
</Hds::PageHeader>

<div class="status-notifications {{if this.canSignIn "is-half"}}">
<div class="status-notifications">

{{#if (eq this.signInStatus "failure")}}
<div data-test-token-error class="notification is-danger">
<div class="columns">
<div class="column">
<h3 class="title is-4">Token Failed to Authenticate</h3>
<p>The token secret you have provided does not match an existing token, or has expired.</p>
</div>
</div>
</div>
<Hds::Alert data-test-token-error @type="inline" @color="critical"
@onDismiss={{action (mut this.signInStatus) null}}
as |A|>
<A.Title>Token Failed to Authenticate</A.Title>
<A.Description>The token secret you have provided does not match an existing token, or has expired.</A.Description>
</Hds::Alert>
{{/if}}

{{#if (eq this.signInStatus "jwtFailure")}}
<div data-test-token-error class="notification is-danger">
<div class="columns">
<div class="column">
<h3 class="title is-4">JWT Failed to Authenticate</h3>
<p>You passed in a JWT, but no JWT auth methods were found</p>
</div>
</div>
</div>
<Hds::Alert data-test-token-error @type="inline" @color="critical"
@onDismiss={{action (mut this.signInStatus) null}}
as |A|>
<A.Title>JWT Failed to Authenticate</A.Title>
<A.Description>You passed in a JWT, but no JWT auth methods were found</A.Description>
</Hds::Alert>
{{/if}}

{{#if this.tokenRecord.isExpired}}
<div data-test-token-expired class="notification is-danger">
<div class="columns">
<div class="column">
<h3 class="title is-4">Your authentication has expired</h3>
<p>Expired {{moment-from-now this.tokenRecord.expirationTime interval=1000}} ({{this.tokenRecord.expirationTime}})</p>
</div>
<div class="column is-centered is-minimum">
<button data-test-token-clear class="button" {{action "clearTokenProperties"}} type="button">Sign In Again</button>
</div>
</div>
</div>
<Hds::Alert
data-test-token-expired
@type="inline"
@color="critical"
@onDismiss={{action "clearTokenProperties"}}
as |A|>
<A.Title>Your authentication has expired</A.Title>
<A.Description>Expired {{moment-from-now this.tokenRecord.expirationTime interval=1000}} ({{this.tokenRecord.expirationTime}})</A.Description>
</Hds::Alert>
{{else}}
{{#if (eq this.signInStatus "success")}}
<div data-test-token-success class="notification is-success">
<div class="columns">
<div class="column">
<h3 class="title is-4">Token Authenticated!</h3>
<p>Your token is valid and authorized for the following policies.</p>
</div>
</div>
</div>
<Hds::Alert @onDismiss={{action (mut this.signInStatus) null}} data-test-token-success @type="inline" @color="success" as |A|>
<A.Title>Token Authenticated!</A.Title>
<A.Description>Your token is valid and authorized for the following policies.</A.Description>
</Hds::Alert>
{{/if}}
{{/if}}

{{#if this.token.tokenNotFound}}
<div data-test-token-not-found class="notification is-danger">
<div class="columns">
<div class="column">
<h3 class="title is-4">Your token was not found</h3>
<p>It may have expired, or been entered incorrectly.</p>
</div>
</div>
</div>
<Hds::Alert data-test-token-not-found @type="inline" @color="critical"
@onDismiss={{action (mut this.token.tokenNotFound) false}}
as |A|>
<A.Title>Token not found</A.Title>
<A.Description>It may have expired, or been entered incorrectly.</A.Description>
</Hds::Alert>
{{/if}}

{{#if this.SSOFailure}}
<div data-test-sso-error class="notification is-danger column">
<div class="columns">
<div class="column">
<h3 class="title is-4">Failed to sign in with SSO</h3>
<p>Your OIDC provider has failed on sign in; please try again or contact your SSO administrator.</p>
</div>
<div class="column is-centered is-minimum">
<button data-test-sso-error-clear class="button" {{action (mut this.state)}} type="button">Clear</button>
</div>
</div>
</div>
<Hds::Alert
data-test-sso-error
@type="inline"
@color="critical"
@onDismiss={{action (mut this.state) null}}
as |A|>
<A.Title>Failed to sign in with SSO</A.Title>
<A.Description>Your OIDC provider has failed on sign in; please try again or contact your SSO administrator.</A.Description>
</Hds::Alert>
{{/if}}
</div>

<div class="columns">
{{#if this.canSignIn}}
<div class="column is-half sign-in-methods">
{{#if this.nonTokenAuthMethods.length}}
<h3 class="title is-4">Sign in with SSO</h3>
<p>Sign in to Nomad using the configured authorization provider. After logging in, the policies and rules for the token will be listed.</p>
<div class="sso-auth-methods">
{{#each this.nonTokenAuthMethods as |method|}}
<button
data-test-auth-method
class="button is-primary"
onclick={{action "redirectToSSO" method}}
type="button"
>Sign in with {{method.name}}
</button>
{{/each}}
</div>
<span class="or-divider"><span>Or</span></span>
{{/if}}
{{#if this.canSignIn}}
<div class="column sign-in-methods">
{{#if this.nonTokenAuthMethods.length}}
<h3 class="title is-4">Sign in with SSO</h3>
<p>Sign in to Nomad using the configured authorization provider. After logging in, the policies and rules for the token will be listed.</p>
<div class="sso-auth-methods">
{{#each this.nonTokenAuthMethods as |method|}}
<Hds::Button
data-test-auth-method
@size="medium"
@text="Sign in with {{method.name}}"
@color="primary"
{{on "click" (action this.redirectToSSO method)}}
/>
{{/each}}
</div>
<span class="or-divider"><span>Or</span></span>
{{/if}}

<h3 class="title is-4">Sign in with token</h3>
<p>Clusters that use Access Control Lists require tokens to perform certain tasks. By providing a token Secret ID{{#if this.hasJWTAuthMethods}} or <a href="https://jwt.io/" target="_blank" rel="noopener noreferrer">JWT</a>{{/if}}, each future request will be authenticated, potentially authorizing read access to additional information.</p>
<label class="label" for="token-input">Secret ID{{#if this.hasJWTAuthMethods}} or JWT{{/if}}</label>
<div class="control {{if (and this.currentSecretIsJWT (gt this.JWTAuthMethods.length 1)) "with-jwt-selector"}}">
<Input
id="token-input"
class="input"
@type="password"
placeholder="{{if this.hasJWTAuthMethods "36-character token secret or JWT" "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"}}"
{{autofocus}}
{{on "input" (action (mut this.secret) value="target.value")}}
@enter={{this.verifyToken}}
data-test-token-secret />
<h3 class="title is-4">Sign in with token</h3>
<p>Clusters that use Access Control Lists require tokens to perform certain tasks. By providing a token Secret ID{{#if this.hasJWTAuthMethods}} or <a href="https://jwt.io/" target="_blank" rel="noopener noreferrer">JWT</a>{{/if}}, each future request will be authenticated, potentially authorizing read access to additional information.</p>
<label class="label" for="token-input">Secret ID{{#if this.hasJWTAuthMethods}} or JWT{{/if}}</label>
<div class="control {{if (and this.currentSecretIsJWT (gt this.JWTAuthMethods.length 1)) "with-jwt-selector"}}">
<Input
id="token-input"
class="input"
@type="password"
placeholder="{{if this.hasJWTAuthMethods "36-character token secret or JWT" "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"}}"
{{autofocus}}
{{on "input" (action (mut this.secret) value="target.value")}}
@enter={{this.verifyToken}}
data-test-token-secret />

{{#if this.currentSecretIsJWT}}
{{did-insert (action this.setCurrentAuthMethod)}}
{{#if (gt this.JWTAuthMethods.length 1)}}
<SingleSelectDropdown
data-test-select-jwt
@label="Sign-in method"
@options={{this.JWTAuthMethodOptions}}
@selection={{this.jwtAuthMethod}}
@onSelect={{fn (mut this.jwtAuthMethod)}}
/>
{{/if}}
{{#if this.currentSecretIsJWT}}
{{did-insert (action this.setCurrentAuthMethod)}}
{{#if (gt this.JWTAuthMethods.length 1)}}
<SingleSelectDropdown
data-test-select-jwt
@label="Sign-in method"
@options={{this.JWTAuthMethodOptions}}
@selection={{this.jwtAuthMethod}}
@onSelect={{fn (mut this.jwtAuthMethod)}}
/>
{{/if}}
</div>
<p class="help">Sent with every request to determine authorization</p>
<button disabled={{not this.secret}} data-test-token-submit class="button is-primary" {{action "verifyToken"}} type="button">
{{#if this.currentSecretIsJWT}}
Sign in with JWT
{{else}}
Sign in with secret
{{/if}}
</button>
{{/if}}
</div>
{{/if}}
<p class="help">Sent with every request to determine authorization</p>
<Hds::Button
disabled={{not this.secret}}
data-test-token-submit
@size="medium"
@text={{if this.currentSecretIsJWT "Sign in with JWT" "Sign in with secret"}}
@color="primary"
{{on "click" this.verifyToken}}
/>
</div>
{{/if}}

{{#if this.shouldShowPolicies}}
<div class="column">
{{#unless this.tokenRecord.isExpired}}
<div class="columns">
<div class="column">
<h3 data-test-token-name class="title is-4">Token: {{this.tokenRecord.name}}</h3>
<div>AccessorID: <code>{{this.tokenRecord.accessor}}</code></div>
<div>SecretID: <code>{{this.tokenRecord.secret}}</code></div>
{{#if this.tokenRecord.expirationTime}}
<div data-test-token-expiry>Expires: {{moment-from-now this.tokenRecord.expirationTime interval=1000}} <span data-test-expiration-timestamp>({{this.tokenRecord.expirationTime}})</span></div>
{{/if}}
</div>
<div class="column is-minimum">
<button data-test-token-clear class="button is-primary" {{action "clearTokenProperties"}} type="button">Sign Out</button>
</div>
</div>

{{#if this.tokenRecord.roles.length}}
{{#if this.shouldShowPolicies}}
<div class="token-details">
{{#unless this.tokenRecord.isExpired}}
<h3 data-test-token-name class="title is-4">Token: {{this.tokenRecord.name}}</h3>
<Hds::Form::MaskedInput::Field readonly @isContentMasked={{false}} @hasCopyButton={{true}} @value={{this.tokenRecord.accessor}} as |F|>
<F.Label>Accessor ID</F.Label>
</Hds::Form::MaskedInput::Field>
<Hds::Form::MaskedInput::Field readonly @hasCopyButton={{true}} @value={{this.tokenRecord.secret}} as |F|>
<F.Label>Secret ID</F.Label>
</Hds::Form::MaskedInput::Field>
{{#if this.tokenRecord.expirationTime}}
<div data-test-token-expiry>Expires: {{moment-from-now this.tokenRecord.expirationTime interval=1000}} <span data-test-expiration-timestamp>({{this.tokenRecord.expirationTime}})</span></div>
{{/if}}
{{#if this.tokenRecord.roles.length}}
<Hds::Separator/>
<div>
<h3 class="title is-4">Roles</h3>
{{#each this.tokenRecord.roles as |role|}}
{{#each this.tokenRecord.roles as |role|}}
<div data-test-token-role class="boxed-section">
<div data-test-role-name class="boxed-section-head">
{{role.name}}
Expand All @@ -187,9 +188,11 @@
</div>
</div>
</div>
{{/each}}
{{/if}}

{{/each}}
</div>
{{/if}}
<Hds::Separator/>
<div>
<h3 class="title is-4">Policies</h3>
{{#if (eq this.tokenRecord.type "management")}}
<div data-test-token-management-message class="boxed-section">
Expand All @@ -216,10 +219,10 @@
</div>
{{/each}}
{{/if}}
{{/unless}}
</div>
{{/if}}
</div>
</div>
{{/unless}}
</div>
{{/if}}

{{/if}}
</section>
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/pages/settings/tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default create({
successMessage: isVisible('[data-test-token-success]'),
managementMessage: isVisible('[data-test-token-management-message]'),
ssoErrorMessage: isVisible('[data-test-sso-error]'),
clearSSOError: clickable('[data-test-sso-error-clear]'),
clearSSOError: clickable('[data-test-sso-error] .hds-dismiss-button'),

policies: collection('[data-test-token-policy]', {
name: text('[data-test-policy-name]'),
Expand Down
Loading