Skip to content

Commit

Permalink
Implements token revocation in Console
Browse files Browse the repository at this point in the history
  • Loading branch information
amrutac committed Apr 17, 2017
1 parent 3ab26e3 commit 33bcc3e
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 6 deletions.
1 change: 1 addition & 0 deletions contrib/dex-config-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ staticPasswords:
hash: "JDJhJDE0JDh4TnlVZ3pzSmVuQm4ySlRPT2QvbmVGcUlnQzF4TEFVRFA3VlpTVzhDNWlkLnFPcmNlYUJX"
username: "admin"
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"

1 change: 1 addition & 0 deletions frontend/public/components/_module.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,4 @@ import './nav';
import './resource-list';
import './container-linux-update-operator/container-linux-updates';
import './container-linux-update-operator/container-linux-update-details';
import './client-tokens';
100 changes: 100 additions & 0 deletions frontend/public/components/client-tokens.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';

import { coFetch, coFetchJSON } from '../co-fetch';
import { SafetyFirst } from './safety-first';
import { confirmModal } from './modals';
import { Cog, Timestamp, EmptyBox } from './utils';

export class ClientTokensContainer extends SafetyFirst {
constructor(props){
super(props);
this.state = {
clients : null
};
this._getClients = this._getClients.bind(this);
}

componentDidMount() {
super.componentDidMount();
this._getClients();
}

_getClients() {
coFetchJSON('tectonic/clients')
.then((clients) => {
this.setState({ clients: _.get(clients, 'token_data', []) || [] });
})
.catch(() => {
this.setState({ clients: null });
});
}

render() {
return <ClientTokens clients={this.state.clients} onTokenRevocation={this._getClients} />;
}
}

const RevokeToken = (id, onTokenRevocation) => ({
label: 'Revoke Access...',
callback: () => confirmModal({
title: 'Confirm Revocation ',
message: 'Revoking this client\'s access token will immediately remove it. Once removed, you cannot recover the token. If this is the last client authorized, you will loose access to the Console after submitting it.',
btnText: 'Revoke Access',
executeFn: () => {
const data = new FormData();
data.append('clientId', id);
const promise = coFetch('tectonic/revoke-token', {
method: 'POST',
body: data
}).then(onTokenRevocation);
return promise;
},
})
});

const ClientCog = ({id, onTokenRevocation}) => {
const options = [
RevokeToken
].map(f => f(id, onTokenRevocation));
return <Cog options={options} />;
};

export const ClientRow = ({client, onTokenRevocation}) => {
return <div className="row co-resource-list__item">
<div className="col-xs-4">
<ClientCog id={client.client_id} onTokenRevocation={onTokenRevocation}/>&nbsp;&nbsp;<span>{client.client_id}</span>
</div>
<div className="col-xs-4">
<Timestamp timestamp={client.created_at} isUnix={true} />
</div>
<div className="col-xs-4">
<Timestamp timestamp={client.last_used} isUnix={true} />
</div>
</div>;
};

export const ClientTokens = ({clients, onTokenRevocation}) => {
if (clients) {
return <div className="co-m-pane">
<div className="co-m-pane__heading">
<h1 className="co-p-cluster--heading">Access Management</h1>
<p>
Manage access that software tools and SDKs have on your behalf. If a client is no longer needed or trusted, revoke its refresh token (refresh_token) to invalidate its ability to obtain new access token (id_token).
</p>
</div>
<div className="co-m-pane__body">
<div className="co-m-table-grid co-m-table-grid--bordered">
<div className="row co-m-table-grid__head">
<div className="col-xs-4">Client Id</div>
<div className="col-xs-4">Token Created</div>
<div className="col-xs-4">Token Last Used</div>
</div>
<div className="co-m-table-grid__body">
{clients.length > 0 ? _.map(clients, (client) => <ClientRow client={client} key={client.id} onTokenRevocation={onTokenRevocation}/>) : <EmptyBox label="Clients" />}
</div>
</div>
</div>
</div>;
}
return null;
};
2 changes: 2 additions & 0 deletions frontend/public/components/profile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Helmet from 'react-helmet';
import {authSvc} from '../module/auth';
import {kubectlConfigModal} from './modals';
import {NavTitle} from './utils';
import {ClientTokensContainer} from './client-tokens';

export const ProfilePage = () => <div className="co-p-profile">
<Helmet title="Profile" />
Expand All @@ -21,5 +22,6 @@ export const ProfilePage = () => <div className="co-p-profile">
</dl>
</div>
</div>
<ClientTokensContainer />
</div>
</div>;
18 changes: 12 additions & 6 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,23 @@ func (s *Server) HTTPHandler() http.Handler {
useVersionHandler := s.versionHandler
useValidateLicenseHandler := s.validateLicenseHandler
useCertsHandler := s.certsHandler
useClientsHandler := s.handleListClients
useTokenRevocationHandler := s.handleTokenRevocation

if !s.AuthDisabled() {
useVersionHandler = authMiddleware(s.Auther, http.HandlerFunc(s.versionHandler))
useValidateLicenseHandler = authMiddleware(s.Auther, http.HandlerFunc(s.validateLicenseHandler))
useCertsHandler = authMiddleware(s.Auther, http.HandlerFunc(s.certsHandler))
useClientsHandler = authMiddleware(s.Auther, http.HandlerFunc(s.handleListClients))
useTokenRevocationHandler = authMiddleware(s.Auther, http.HandlerFunc(s.handleTokenRevocation))
}

handleFunc("/version", useVersionHandler)
handleFunc("/license/validate", useValidateLicenseHandler)
handleFunc("/tectonic/ldap/validate", handleLDAPVerification)
mux.HandleFunc("/tectonic/certs", useCertsHandler)
mux.HandleFunc("/tectonic/clients", useClientsHandler)
mux.HandleFunc("/tectonic/revoke-token", useTokenRevocationHandler)
mux.HandleFunc(s.BaseURL.Path, s.indexHandler)

return http.Handler(mux)
Expand Down Expand Up @@ -529,7 +535,12 @@ func (s *Server) handleTokenRevocation(w http.ResponseWriter, r *http.Request) {
return
}

clientID := r.FormValue("client_id")
clientID := r.FormValue("clientId")
if clientID == "" {
sendResponse(w, http.StatusBadRequest, apiError{"Failed to revoke refresh token: client_id not provided"})
return
}

userID, err := extractUserIdFromCookie(s.Auther, r)
if err != nil {
sendResponse(w, http.StatusBadRequest, apiError{fmt.Sprintf("Failed to revoke refresh token: %v", err)})
Expand All @@ -541,11 +552,6 @@ func (s *Server) handleTokenRevocation(w http.ResponseWriter, r *http.Request) {
return
}

if clientID == "" {
sendResponse(w, http.StatusBadRequest, apiError{"Failed to revoke refresh token: client_id not provided"})
return
}

req := &api.RevokeRefreshReq{
UserId: userID,
ClientId: clientID,
Expand Down

0 comments on commit 33bcc3e

Please sign in to comment.