Skip to content

Commit ed47f2e

Browse files
committed
prevent bearer tokens or username/passwords to be stored in browser local storage
* for security reasons
1 parent 31f31d1 commit ed47f2e

File tree

6 files changed

+98
-63
lines changed

6 files changed

+98
-63
lines changed

documentation/src/main/resources/pages/ditto/user-interface.md

+3-9
Original file line numberDiff line numberDiff line change
@@ -146,25 +146,19 @@ export type OidcAuthSettings = {
146146
/** Whether to automatically start SSO when the Authorize popup model loads */
147147
autoSso: boolean,
148148
/** The actually chosen OIDC provider (which can be changed by the user in the frontend) - must match a key in "AuthSettings.oidc" */
149-
provider?: string,
150-
/** The cached bearer token obtained via SSO */
151-
bearerToken?: string
149+
provider?: string
152150
}
153151

154152
type BasicAuthSettings = {
155153
/** Whether the Basic Auth section should be enabled in the Authorize popup */
156154
enabled: boolean,
157155
/** The default username and password to pre-configure */
158-
defaultUsernamePassword: string | null,
159-
/** The cached username and password */
160-
usernamePassword?: string
156+
defaultUsernamePassword: string | null
161157
}
162158

163159
type BearerAuthSettings = {
164160
/** Whether the Bearer Auth section should be enabled in the Authorize popup */
165-
enabled: boolean,
166-
/** The cached bearer token */
167-
bearerToken?: string
161+
enabled: boolean
168162
}
169163

170164
type PreAuthSettings = {

ui/modules/api.ts

+26-12
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
*/
1414

1515
import { EventSourcePolyfill } from 'event-source-polyfill';
16+
import {
17+
devopsBearerToken,
18+
devopsOidcBearerToken,
19+
devopsUsernamePassword,
20+
mainBearerToken,
21+
mainOidcBearerToken,
22+
mainUsernamePassword
23+
} from './environments/authorization';
1624
import * as Environments from './environments/environments.js';
1725
import { AuthMethod } from './environments/environments.js';
1826
import * as Utils from './utils.js';
@@ -286,23 +294,26 @@ export function setAuthHeader(forDevOps: boolean) {
286294
if (forDevOps) {
287295
let devopsAuthMethod = environment.authSettings?.devops?.method;
288296
if (devopsAuthMethod === AuthMethod.basic) {
289-
if (environment.authSettings.devops.basic.usernamePassword) {
297+
let devopsUserPass = devopsUsernamePassword();
298+
if (devopsUserPass) {
290299
authHeaderKey = 'Authorization';
291-
authHeaderValue = 'Basic ' + window.btoa(environment.authSettings.devops.basic.usernamePassword);
300+
authHeaderValue = 'Basic ' + window.btoa(devopsUserPass);
292301
} else {
293302
showError('DevOps Username/password missing')
294303
}
295304
} else if (devopsAuthMethod === AuthMethod.bearer) {
296-
if (environment.authSettings.devops.bearer.bearerToken) {
305+
let devopsToken = devopsBearerToken();
306+
if (devopsToken) {
297307
authHeaderKey = 'Authorization';
298-
authHeaderValue = 'Bearer ' + environment.authSettings.devops.bearer.bearerToken;
308+
authHeaderValue = 'Bearer ' + devopsToken;
299309
} else {
300310
showError('DevOps Bearer token missing')
301311
}
302312
} else if (devopsAuthMethod === AuthMethod.oidc) {
303-
if (environment.authSettings.devops.oidc.bearerToken) {
313+
let devopsOidcToken = devopsOidcBearerToken();
314+
if (devopsOidcToken) {
304315
authHeaderKey = 'Authorization';
305-
authHeaderValue = 'Bearer ' + environment.authSettings.devops.oidc.bearerToken;
316+
authHeaderValue = 'Bearer ' + devopsOidcToken;
306317
} else {
307318
showError('DevOps SSO (Bearer) token missing')
308319
}
@@ -313,9 +324,10 @@ export function setAuthHeader(forDevOps: boolean) {
313324
} else {
314325
let mainAuthMethod = environment.authSettings?.main?.method;
315326
if (mainAuthMethod === AuthMethod.basic) {
316-
if (environment.authSettings.main.basic.usernamePassword) {
327+
let mainUserPass = mainUsernamePassword();
328+
if (mainUserPass) {
317329
authHeaderKey = 'Authorization';
318-
authHeaderValue = 'Basic ' + window.btoa(environment.authSettings.main.basic.usernamePassword);
330+
authHeaderValue = 'Basic ' + window.btoa(mainUserPass);
319331
} else {
320332
showError('Username/password missing')
321333
}
@@ -327,16 +339,18 @@ export function setAuthHeader(forDevOps: boolean) {
327339
showError('Pre-Authenticated username missing')
328340
}
329341
} else if (mainAuthMethod === AuthMethod.bearer) {
330-
if (environment.authSettings.main.bearer.bearerToken) {
342+
let mainToken = mainBearerToken();
343+
if (mainToken) {
331344
authHeaderKey = 'Authorization';
332-
authHeaderValue = 'Bearer ' + environment.authSettings.main.bearer.bearerToken;
345+
authHeaderValue = 'Bearer ' + mainToken;
333346
} else {
334347
showError('Bearer token missing')
335348
}
336349
} else if (mainAuthMethod === AuthMethod.oidc) {
337-
if (environment.authSettings.main.oidc.bearerToken) {
350+
let mainOidcToken = mainOidcBearerToken();
351+
if (mainOidcToken) {
338352
authHeaderKey = 'Authorization';
339-
authHeaderValue = 'Bearer ' + environment.authSettings.main.oidc.bearerToken;
353+
authHeaderValue = 'Bearer ' + mainOidcToken;
340354
} else {
341355
showError('SSO (Bearer) token missing')
342356
}

ui/modules/environments/authorization.html

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ <h5>Main authentication</h5>
3030
<div class="input-group">
3131
<label for="oidcProvider" class="input-group-text"><small>SSO provider</small></label>
3232
<select class="form-select form-select-sm me-2" id="oidcProvider"></select>
33+
<input type="hidden" class="form-control form-control-sm" id="oidcBearer" name="oidcBearer" />
3334
<button class="btn btn-outline-secondary btn-sm" id="main-oidc-login" data-bs-dismiss="modal">Login</button>
3435
<button class="btn btn-outline-secondary btn-sm" id="main-oidc-logout" data-bs-dismiss="modal">Logout</button>
3536
</div>
@@ -92,6 +93,7 @@ <h5>DevOps authentication</h5>
9293
<div class="input-group">
9394
<label for="devOpsOidcProvider" class="input-group-text"><small>SSO provider</small></label>
9495
<select class="form-select form-select-sm me-2" id="devOpsOidcProvider"></select>
96+
<input type="hidden" class="form-control form-control-sm" id="oidcBearerDevOps" name="oidcBearerDevOps" />
9597
<button class="btn btn-outline-secondary btn-sm" id="devops-oidc-login" data-bs-dismiss="modal">Login</button>
9698
<button class="btn btn-outline-secondary btn-sm" id="devops-oidc-logout" data-bs-dismiss="modal">Logout</button>
9799
</div>

ui/modules/environments/authorization.ts

+56-22
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ import {
3131

3232
let dom = {
3333
mainBearerSection: null,
34+
oidcBearer: null,
3435
bearer: null,
3536
mainBasicSection: null,
3637
userName: null,
3738
password: null,
3839
devOpsBearerSection: null,
40+
oidcBearerDevOps: null,
3941
bearerDevOps: null,
4042
devOpsBasicSection: null,
4143
devOpsUserName: null,
@@ -67,6 +69,52 @@ export function setForDevops(forDevops: boolean) {
6769
API.setAuthHeader(_forDevops);
6870
}
6971

72+
export function mainUsernamePassword(): string {
73+
return dom.userName.value + ':' + dom.password.value;
74+
}
75+
76+
export function mainBearerToken(): string {
77+
return dom.bearer.value;
78+
}
79+
80+
export function mainOidcBearerToken(): string {
81+
return dom.oidcBearer.value;
82+
}
83+
84+
export function devopsUsernamePassword(): string {
85+
return dom.devOpsUserName.value + ':' + dom.devOpsPassword.value;
86+
}
87+
88+
export function devopsBearerToken(): string {
89+
return dom.bearerDevOps.value;
90+
}
91+
92+
export function devopsOidcBearerToken(): string {
93+
return dom.oidcBearerDevOps.value;
94+
}
95+
96+
export function fillMainUsernamePassword(usernamePassword: string) {
97+
if (usernamePassword && usernamePassword.length > 0) {
98+
dom.userName.value = usernamePassword.split(':')[0];
99+
dom.password.value = usernamePassword.split(':')[1];
100+
}
101+
}
102+
103+
export function fillDevopsUsernamePassword(usernamePassword: string) {
104+
if (usernamePassword && usernamePassword.length > 0) {
105+
dom.devOpsUserName.value = usernamePassword.split(':')[0];
106+
dom.devOpsPassword.value = usernamePassword.split(':')[1];
107+
}
108+
}
109+
110+
function fillMainOidcBearerToken(oidcToken: string) {
111+
dom.oidcBearer.value = oidcToken;
112+
}
113+
114+
function fillDevopsOidcBearerToken(oidcToken: string) {
115+
dom.oidcBearerDevOps.value = oidcToken;
116+
}
117+
70118
export function ready() {
71119
Utils.getAllElementsById(dom);
72120

@@ -111,10 +159,6 @@ export function ready() {
111159
let environment = Environments.current();
112160
environment.authSettings.main.method = AuthMethod[mainAuthMethod as keyof typeof AuthMethod];
113161
environment.authSettings.devops.method = AuthMethod[devopsAuthMethod as keyof typeof AuthMethod];
114-
environment.authSettings.main.basic.usernamePassword = dom.userName.value + ':' + dom.password.value;
115-
environment.authSettings.devops.basic.usernamePassword = dom.devOpsUserName.value + ':' + dom.devOpsPassword.value;
116-
environment.authSettings.main.bearer.bearerToken = dom.bearer.value;
117-
environment.authSettings.devops.bearer.bearerToken = dom.bearerDevOps.value;
118162
environment.authSettings.main.pre.dittoPreAuthenticatedUsername = dom.dittoPreAuthenticatedUsername.value;
119163
environment.authSettings.main.oidc.defaultProvider = dom.oidcProvider.value;
120164
environment.authSettings.devops.oidc.defaultProvider = dom.devOpsOidcProvider.value;
@@ -181,12 +225,12 @@ async function handleSingleSignOnCallback(urlSearchParams: URLSearchParams) {
181225
if (user) {
182226
let oidcState = user.state as OidcState
183227
if (oidcState.mainAuth) {
184-
environment.authSettings.main.method = AuthMethod.oidc
185-
environment.authSettings.main.oidc.bearerToken = user[oidcProvider.extractBearerTokenFrom]
228+
environment.authSettings.main.method = AuthMethod.oidc;
229+
fillMainOidcBearerToken(user[oidcProvider.extractBearerTokenFrom]);
186230
}
187231
if (oidcState.devopsAuth) {
188-
environment.authSettings.devops.method = AuthMethod.oidc
189-
environment.authSettings.devops.oidc.bearerToken = user[oidcProvider.extractBearerTokenFrom]
232+
environment.authSettings.devops.method = AuthMethod.oidc;
233+
fillDevopsOidcBearerToken(user[oidcProvider.extractBearerTokenFrom]);
190234
}
191235
window.history.replaceState(null, null, `${settings.redirect_uri}?${atob(user.url_state)}`)
192236
await Environments.environmentsJsonChanged(false)
@@ -220,10 +264,10 @@ async function performSingleSignOn(forMainAuth: boolean): Promise<boolean> {
220264
if (user?.[oidcProvider.extractBearerTokenFrom] !== undefined || user?.expired === true) {
221265
// a user is still logged in via a valid token stored in the browser's session storage
222266
if (sameProviderForMainAndDevops) {
223-
environment.authSettings.main.oidc.bearerToken = user[oidcProvider.extractBearerTokenFrom]
224-
environment.authSettings.devops.oidc.bearerToken = user[oidcProvider.extractBearerTokenFrom]
267+
fillMainOidcBearerToken(user[oidcProvider.extractBearerTokenFrom]);
268+
fillDevopsOidcBearerToken(user[oidcProvider.extractBearerTokenFrom]);
225269
} else {
226-
oidc.bearerToken = user[oidcProvider.extractBearerTokenFrom]
270+
fillMainOidcBearerToken(user[oidcProvider.extractBearerTokenFrom]);
227271
}
228272
return true
229273
} else {
@@ -268,7 +312,7 @@ async function performSingleSignOut(oidc: OidcAuthSettings) {
268312
} catch (e) {
269313
showError(e)
270314
} finally {
271-
oidc.bearerToken = undefined
315+
fillMainOidcBearerToken(undefined);
272316
Environments.saveEnvironmentsToLocalStorage();
273317
}
274318
}
@@ -284,16 +328,6 @@ function dynamicallyShowOrHideSection(sectionEnabled: boolean, section: HTMLElem
284328

285329
export async function onEnvironmentChanged(initialPageLoad: boolean) {
286330
let environment = Environments.current();
287-
let usernamePassword = environment.authSettings?.main?.basic?.usernamePassword ?
288-
environment.authSettings?.main?.basic?.usernamePassword : ':';
289-
dom.userName.value = usernamePassword.split(':')[0];
290-
dom.password.value = usernamePassword.split(':')[1];
291-
usernamePassword = environment.authSettings?.devops?.basic?.usernamePassword ?
292-
environment.authSettings?.devops?.basic?.usernamePassword : ':';
293-
dom.devOpsUserName.value = usernamePassword.split(':')[0];
294-
dom.devOpsPassword.value = usernamePassword.split(':')[1];
295-
dom.bearer.value = environment.authSettings?.main?.bearer?.bearerToken ? environment.authSettings?.main?.bearer?.bearerToken : '';
296-
dom.bearerDevOps.value = environment.authSettings?.devops?.bearer?.bearerToken ? environment.authSettings?.devops?.bearer?.bearerToken : '';
297331
dom.dittoPreAuthenticatedUsername.value = environment.authSettings?.main?.pre?.dittoPreAuthenticatedUsername ?
298332
environment.authSettings?.main?.pre?.dittoPreAuthenticatedUsername : '';
299333
if (environment.authSettings?.oidc?.providers) {

ui/modules/environments/environments.ts

+8-11
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as Utils from '../utils.js';
1717
/* eslint-disable prefer-const */
1818
/* eslint-disable require-jsdoc */
1919
import * as Authorization from './authorization.js';
20+
import { fillDevopsUsernamePassword, fillMainUsernamePassword } from './authorization.js';
2021
import environmentsHTML from './environments.html';
2122
import defaultTemplates from './environmentTemplates.json';
2223

@@ -50,16 +51,12 @@ type BasicAuthSettings = {
5051
/** Whether the Basic Auth section should be enabled in the Authorize popup */
5152
enabled: boolean,
5253
/** The default username and password to pre-configure */
53-
defaultUsernamePassword: string | null,
54-
/** The cached username and password */
55-
usernamePassword?: string
54+
defaultUsernamePassword: string | null
5655
}
5756

5857
type BearerAuthSettings = {
5958
/** Whether the Bearer Auth section should be enabled in the Authorize popup */
60-
enabled: boolean,
61-
/** The cached bearer token */
62-
bearerToken?: string
59+
enabled: boolean
6360
}
6461

6562
type PreAuthSettings = {
@@ -161,11 +158,11 @@ document.getElementById('environmentsHTML').innerHTML = environmentsHTML;
161158

162159
function Environment(env: Environment): void {
163160
Object.assign(this, env);
164-
this.authSettings.main.oidc.provider = env.authSettings?.main?.oidc?.defaultProvider
165-
this.authSettings.main.basic.usernamePassword = env.authSettings?.main?.basic?.defaultUsernamePassword
166-
this.authSettings.main.pre.dittoPreAuthenticatedUsername = env.authSettings?.main?.pre?.defaultDittoPreAuthenticatedUsername
167-
this.authSettings.devops.oidc.provider = env.authSettings?.devops?.oidc?.defaultProvider
168-
this.authSettings.devops.basic.usernamePassword = env.authSettings?.devops?.basic?.defaultUsernamePassword
161+
this.authSettings.main.oidc.provider = env.authSettings?.main?.oidc?.defaultProvider;
162+
fillMainUsernamePassword(env.authSettings?.main?.basic?.defaultUsernamePassword);
163+
this.authSettings.main.pre.dittoPreAuthenticatedUsername = env.authSettings?.main?.pre?.defaultDittoPreAuthenticatedUsername;
164+
this.authSettings.devops.oidc.provider = env.authSettings?.devops?.oidc?.defaultProvider;
165+
fillDevopsUsernamePassword(env.authSettings?.devops?.basic?.defaultUsernamePassword);
169166
}
170167

171168
export function currentEnvironmentSelector() {

ui/readme.md

+3-9
Original file line numberDiff line numberDiff line change
@@ -99,25 +99,19 @@ export type OidcAuthSettings = {
9999
/** Whether to automatically start SSO when the Authorize popup model loads */
100100
autoSso: boolean,
101101
/** The actually chosen OIDC provider (which can be changed by the user in the frontend) - must match a key in "AuthSettings.oidc" */
102-
provider?: string,
103-
/** The cached bearer token obtained via SSO */
104-
bearerToken?: string
102+
provider?: string
105103
}
106104

107105
type BasicAuthSettings = {
108106
/** Whether the Basic Auth section should be enabled in the Authorize popup */
109107
enabled: boolean,
110108
/** The default username and password to pre-configure */
111-
defaultUsernamePassword: string | null,
112-
/** The cached username and password */
113-
usernamePassword?: string
109+
defaultUsernamePassword: string | null
114110
}
115111

116112
type BearerAuthSettings = {
117113
/** Whether the Bearer Auth section should be enabled in the Authorize popup */
118-
enabled: boolean,
119-
/** The cached bearer token */
120-
bearerToken?: string
114+
enabled: boolean
121115
}
122116

123117
type PreAuthSettings = {

0 commit comments

Comments
 (0)