github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/app/controllers/settings/tokens.js (about) 1 // @ts-check 2 import { inject as service } from '@ember/service'; 3 import { reads } from '@ember/object/computed'; 4 import Controller from '@ember/controller'; 5 import { getOwner } from '@ember/application'; 6 import { alias } from '@ember/object/computed'; 7 import { action } from '@ember/object'; 8 import classic from 'ember-classic-decorator'; 9 import { tracked } from '@glimmer/tracking'; 10 import Ember from 'ember'; 11 12 @classic 13 export default class Tokens extends Controller { 14 @service token; 15 @service store; 16 @service router; 17 18 queryParams = ['code', 'state']; 19 20 @reads('token.secret') secret; 21 22 /** 23 * @type {(null | "success" | "failure")} signInStatus 24 */ 25 @tracked 26 signInStatus = null; 27 28 @alias('token.selfToken') tokenRecord; 29 30 resetStore() { 31 this.store.unloadAll(); 32 } 33 34 @action 35 clearTokenProperties() { 36 this.token.setProperties({ 37 secret: undefined, 38 tokenNotFound: false, 39 }); 40 this.signInStatus = null; 41 // Clear out all data to ensure only data the anonymous token is privileged to see is shown 42 this.resetStore(); 43 this.token.reset(); 44 this.store.findAll('auth-method'); 45 } 46 47 get authMethods() { 48 return this.store.peekAll('auth-method'); 49 } 50 51 @action 52 verifyToken() { 53 const { secret } = this; 54 this.clearTokenProperties(); 55 const TokenAdapter = getOwner(this).lookup('adapter:token'); 56 57 this.set('token.secret', secret); 58 this.set('secret', null); 59 60 TokenAdapter.findSelf().then( 61 () => { 62 // Clear out all data to ensure only data the new token is privileged to see is shown 63 this.resetStore(); 64 65 // Refetch the token and associated policies 66 this.get('token.fetchSelfTokenAndPolicies').perform().catch(); 67 68 this.signInStatus = 'success'; 69 this.token.set('tokenNotFound', false); 70 }, 71 () => { 72 this.set('token.secret', undefined); 73 this.signInStatus = 'failure'; 74 } 75 ); 76 } 77 78 // Generate a 20-char nonce, using window.crypto to 79 // create a sufficiently-large output then trimming 80 generateNonce() { 81 let randomArray = new Uint32Array(10); 82 crypto.getRandomValues(randomArray); 83 return randomArray.join('').slice(0, 20); 84 } 85 86 @action redirectToSSO(method) { 87 const provider = method.name; 88 const nonce = this.generateNonce(); 89 90 window.localStorage.setItem('nomadOIDCNonce', nonce); 91 window.localStorage.setItem('nomadOIDCAuthMethod', provider); 92 93 method 94 .getAuthURL({ 95 AuthMethod: provider, 96 ClientNonce: nonce, 97 RedirectUri: Ember.testing 98 ? this.router.currentURL 99 : window.location.toString(), 100 }) 101 .then(({ AuthURL }) => { 102 if (Ember.testing) { 103 this.router.transitionTo(AuthURL.split('/ui')[1]); 104 } else { 105 window.location = AuthURL; 106 } 107 }); 108 } 109 110 @tracked code = null; 111 @tracked state = null; 112 113 get isValidatingToken() { 114 if (this.code && this.state === 'success') { 115 this.validateSSO(); 116 return true; 117 } else { 118 return false; 119 } 120 } 121 122 async validateSSO() { 123 const res = await this.token.authorizedRequest( 124 '/v1/acl/oidc/complete-auth', 125 { 126 method: 'POST', 127 body: JSON.stringify({ 128 AuthMethod: window.localStorage.getItem('nomadOIDCAuthMethod'), 129 ClientNonce: window.localStorage.getItem('nomadOIDCNonce'), 130 Code: this.code, 131 State: this.state, 132 }), 133 } 134 ); 135 136 if (res.ok) { 137 const data = await res.json(); 138 this.token.set('secret', data.ACLToken); 139 this.verifyToken(); 140 this.state = null; 141 this.code = null; 142 } else { 143 this.state = 'failure'; 144 this.code = null; 145 } 146 } 147 148 get SSOFailure() { 149 return this.state === 'failure'; 150 } 151 152 get canSignIn() { 153 return !this.tokenRecord || this.tokenRecord.isExpired; 154 } 155 156 get shouldShowPolicies() { 157 return this.tokenRecord; 158 } 159 }