mirror of
https://github.com/Joxit/docker-registry-ui.git
synced 2025-04-27 07:29:54 +03:00
feat(token-auth-keycloak): remove custom dialog and use browser basic auth with nginx configuration
This commit is contained in:
parent
58b1486c81
commit
bc5082dcf9
7 changed files with 53 additions and 98 deletions
|
@ -34,6 +34,17 @@ server {
|
|||
proxy_pass http://keycloak:8080;
|
||||
}
|
||||
|
||||
location /auth/realms/master/protocol/docker-v2/auth {
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Host $host;
|
||||
if ($http_authorization = "") {
|
||||
add_header WWW-Authenticate 'Basic realm="Keycloak login"' always;
|
||||
return 401;
|
||||
}
|
||||
proxy_pass http://keycloak:8080;
|
||||
}
|
||||
|
||||
location /ui {
|
||||
proxy_pass http://ui/;
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
<!--
|
||||
Copyright (C) 2016-2021 Jones Magloire @Joxit
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<registry-authentication>
|
||||
<material-popup opened="{ props.opened }" onClick="{ props.onClose }">
|
||||
<div slot="title">Sign In to your registry</div>
|
||||
<div slot="content">
|
||||
<material-input placeholder="Username" id="username"></material-input>
|
||||
<material-input placeholder="Password" id="password" onkeyup="{ onKeyUp }" type="password"></material-input>
|
||||
</div>
|
||||
<div slot="action">
|
||||
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ signIn }">
|
||||
Sign In
|
||||
</material-button>
|
||||
<material-button class="dialog-button" waves-color="rgba(158,158,158,.4)" onClick="{ props.onClose }">
|
||||
Cancel
|
||||
</material-button>
|
||||
</div>
|
||||
</material-popup>
|
||||
<script>
|
||||
export default {
|
||||
signIn() {
|
||||
const {
|
||||
realm,
|
||||
service,
|
||||
scope,
|
||||
onAuthenticated,
|
||||
onClose
|
||||
} = this.props;
|
||||
const username = this.$('#username input').value;
|
||||
const password = this.$('#password input').value;
|
||||
const req = new XMLHttpRequest()
|
||||
req.addEventListener('loadend', () => {
|
||||
try {
|
||||
const bearer = JSON.parse(req.responseText);
|
||||
onAuthenticated(bearer)
|
||||
localStorage.setItem('registry.token', bearer.token);
|
||||
localStorage.setItem('registry.issued_at', bearer.issued_at);
|
||||
localStorage.setItem('registry.expires_in', bearer.expires_in);
|
||||
onClose();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
})
|
||||
req.open('GET', `${realm}?service=${service}&scope=${scope}`)
|
||||
req.setRequestHeader('Authorization', `Basic ${btoa(`${username}:${password}`)}`)
|
||||
req.send()
|
||||
},
|
||||
onKeyUp(event) {
|
||||
// if keyCode is Enter
|
||||
if (event.keyCode === 13) {
|
||||
this.signIn();
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
</registry-authentication>
|
|
@ -39,7 +39,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
<route path="{baseRoute}taghistory/(.*)">
|
||||
<tag-history registry-url="{ state.registryUrl }" registry-name="{ state.name }" pull-url="{ state.pullUrl }"
|
||||
image="{ router.getTagHistoryImage() }" tag="{ router.getTagHistoryTag() }"
|
||||
is-image-remove-activated="{ props.isImageRemoveActivated }" on-notify="{ notifySnackbar }" on-authentication="{ onAuthentication }"></tag-history>
|
||||
is-image-remove-activated="{ props.isImageRemoveActivated }" on-notify="{ notifySnackbar }"
|
||||
on-authentication="{ onAuthentication }"></tag-history>
|
||||
</route>
|
||||
</router>
|
||||
<registry-authentication realm="{ state.realm }" scope="{ state.scope }" service="{ state.service }"
|
||||
|
@ -73,7 +74,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
import TagHistory from './tag-history/tag-history.riot';
|
||||
import DialogsMenu from './dialogs/dialogs-menu.riot';
|
||||
import SearchBar from './search-bar.riot'
|
||||
import RegistryAuthentication from './dialogs/registry-authentication.riot';
|
||||
import {
|
||||
stripHttps,
|
||||
getRegistryServers
|
||||
|
@ -87,7 +87,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
TagHistory,
|
||||
DialogsMenu,
|
||||
SearchBar,
|
||||
RegistryAuthentication,
|
||||
Router,
|
||||
Route
|
||||
},
|
||||
|
@ -113,20 +112,23 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
})
|
||||
},
|
||||
onAuthentication(opts, onAuthenticated) {
|
||||
const bearer = {
|
||||
token: localStorage.getItem('registry.token'),
|
||||
issued_at: localStorage.getItem('registry.issued_at'),
|
||||
expires_in: localStorage.getItem('registry.expires_in')
|
||||
}
|
||||
if (bearer.token && bearer.issued_at && bearer.expires_in &&
|
||||
(new Date().getTime() - new Date(bearer.issued_at).getTime()) < (bearer.expires_in * 1000)) {
|
||||
onAuthenticated(bearer)
|
||||
} else if (opts) {
|
||||
this.update({
|
||||
authenticationDialogOpened: true,
|
||||
onAuthenticated,
|
||||
...opts
|
||||
if (opts && opts.realm && opts.service && opts.scope) {
|
||||
const {
|
||||
realm,
|
||||
service,
|
||||
scope,
|
||||
} = opts;
|
||||
const req = new XMLHttpRequest()
|
||||
req.addEventListener('loadend', () => {
|
||||
try {
|
||||
const bearer = JSON.parse(req.responseText);
|
||||
onAuthenticated(bearer)
|
||||
} catch (e) {
|
||||
this.notifySnackbar(`Failed to log in: ${e.message}`, true)
|
||||
}
|
||||
})
|
||||
req.open('GET', `${realm}?service=${service}&scope=${scope}`)
|
||||
req.send()
|
||||
} else {
|
||||
onAuthenticated()
|
||||
}
|
||||
|
|
|
@ -43,21 +43,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
state.checked = props.checked;
|
||||
},
|
||||
deleteImage(ignoreError) {
|
||||
deleteImage(this.props.image, this.props.registryUrl, ignoreError, this.props.onNotify)
|
||||
deleteImage(this.props.image, {
|
||||
...this.props,
|
||||
ignoreError
|
||||
})
|
||||
},
|
||||
handleCheckboxChange(checked) {
|
||||
this.props.handleCheckboxChange(checked, this.props.image);
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteImage(image, registryUrl, ignoreError, onNotify) {
|
||||
export function deleteImage(image, opts) {
|
||||
const {
|
||||
registryUrl,
|
||||
ignoreError,
|
||||
onNotify,
|
||||
onAuthentication
|
||||
} = opts;
|
||||
if (!image.digest) {
|
||||
onNotify(`Information for ${name}:${tag} are not yet loaded.`);
|
||||
return;
|
||||
}
|
||||
const name = image.name;
|
||||
const tag = image.tag;
|
||||
const oReq = new Http();
|
||||
const oReq = new Http({
|
||||
onAuthentication: onAuthentication
|
||||
});
|
||||
oReq.addEventListener('loadend', function () {
|
||||
if (this.status == 200 || this.status == 202) {
|
||||
router.taglist(name);
|
||||
|
@ -71,7 +82,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
onNotify(this.responseText);
|
||||
}
|
||||
});
|
||||
oReq.open('DELETE', registryUrl + '/v2/' + name + '/manifests/' + image.digest);
|
||||
oReq.open('DELETE', `${registryUrl}/v2/${name}/manifests/${image.digest}`);
|
||||
oReq.setRequestHeader('Accept',
|
||||
'application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json');
|
||||
oReq.addEventListener('error', function () {
|
||||
|
|
|
@ -39,7 +39,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
<tag-table if="{ state.loadend }" tags="{state.tags}" asc="{state.asc}" page="{ state.page }"
|
||||
show-content-digest="{props.showContentDigest}" is-image-remove-activated="{props.isImageRemoveActivated}"
|
||||
onReverseOrder="{ onReverseOrder }" registry-url="{ props.registryUrl }" pull-url="{ props.pullUrl }"
|
||||
on-notify="{ props.onNotify }" filter-results="{ props.filterResults }"></tag-table>
|
||||
on-notify="{ props.onNotify }" filter-results="{ props.filterResults }"
|
||||
on-authentication="{ props.onAuthentication }">
|
||||
</tag-table>
|
||||
|
||||
<pagination pages="{ getPageLabels(state.page, getNumPages(state.tags)) }" onPageUpdate="{onPageUpdate}"></pagination>
|
||||
|
||||
|
|
|
@ -63,16 +63,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
<td if="{ props.isImageRemoveActivated }" class="remove-tag">
|
||||
<remove-image multi-delete="{ state.multiDelete }" image="{ image }" registry-url="{ props.registryUrl }"
|
||||
handleCheckboxChange="{ onRemoveImageChange }" checked="{ state.toDelete.has(image) }"
|
||||
on-notify="{ props.onNotify }" />
|
||||
on-notify="{ props.onNotify }" on-authentication="{ props.onAuthentication }" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</material-card>
|
||||
<script>
|
||||
import {
|
||||
Http
|
||||
} from '../../scripts/http';
|
||||
import {
|
||||
getPage,
|
||||
} from '../../scripts/utils';
|
||||
|
@ -112,7 +109,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
state.page = props.page
|
||||
},
|
||||
bulkDelete() {
|
||||
this.state.toDelete.forEach(image => deleteImage(image, this.props.registryUrl, true, this.props.onNotify))
|
||||
this.state.toDelete.forEach(image => deleteImage(image, {
|
||||
...this.props,
|
||||
ignoreError: true
|
||||
}))
|
||||
},
|
||||
onRemoveImageHeaderChange(checked, event) {
|
||||
if (event.altKey === true) {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
<body>
|
||||
<!-- build:keep production -->
|
||||
<docker-registry-ui registry-url="${URL}" name="${REGISTRY_TITLE}" pull-url="${PULL_URL}"
|
||||
<docker-registry-ui registry-url="${REGISTRY_URL}" name="${REGISTRY_TITLE}" pull-url="${PULL_URL}"
|
||||
show-content-digest="${SHOW_CONTENT_DIGEST}" is-image-remove-activated="${DELETE_IMAGES}"
|
||||
catalog-elements-limit="${CATALOG_ELEMENTS_LIMIT}" single-registry="${SINGLE_REGISTRY}">
|
||||
</docker-registry-ui>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue