Skip to content
Snippets Groups Projects
Unverified Commit cd83e4ff authored by Martin Weise's avatar Martin Weise
Browse files

Added eye candy for last used token

parent 7031be4a
Branches
No related tags found
4 merge requests!129New module for citation as they occur multiple,!121Modified logging, modified logging level, modified flasgger endpoint,!113Resolve "Bugs related with Query Service",!111Resolve "Frontend errors and authorized dev keys"
...@@ -84,16 +84,17 @@ public class TokenEndpoint { ...@@ -84,16 +84,17 @@ public class TokenEndpoint {
.body(dto); .body(dto);
} }
@DeleteMapping("/{hash}") @DeleteMapping("/{id}")
@Transactional @Transactional
@Timed(value = "token.delete", description = "Time needed to delete the developer tokens") @Timed(value = "token.delete", description = "Time needed to delete the developer tokens")
@Operation(summary = "Delete developer token", security = @SecurityRequirement(name = "bearerAuth")) @Operation(summary = "Delete developer token", security = @SecurityRequirement(name = "bearerAuth"))
public void delete(@NotNull @PathVariable("hash") String hash, public void delete(@NotNull @PathVariable("id") Long id,
@NotNull Principal principal) throws TokenNotFoundException, UserNotFoundException { @NotNull Principal principal) throws TokenNotFoundException, UserNotFoundException {
log.debug("endpoint delete developer token, hash={}, principal={}", hash, principal); log.debug("endpoint delete developer token, id={}, principal={}", id, principal);
final Token token = tokenService.findOne(hash); final Token token = tokenService.findOne(id);
log.trace("found token {}", token); log.trace("found token {}", token);
tokenService.delete(token.getTokenHash(), principal); tokenService.delete(token.getTokenHash(), principal);
log.info("Deleted token with id {}", id);
} }
} }
\ No newline at end of file
...@@ -42,6 +42,15 @@ public interface TokenService { ...@@ -42,6 +42,15 @@ public interface TokenService {
*/ */
Token findOne(String tokenHash) throws TokenNotFoundException; Token findOne(String tokenHash) throws TokenNotFoundException;
/**
* Finds a token by id.
*
* @param id The token id.
* @return The token, if successful.
* @throws TokenNotFoundException The token was not found in the metadata database.
*/
Token findOne(Long id) throws TokenNotFoundException;
/** /**
* Deletes a developer token in the metadata database by hash and user principal. * Deletes a developer token in the metadata database by hash and user principal.
* *
......
...@@ -81,6 +81,17 @@ public class TokenServiceImpl implements TokenService { ...@@ -81,6 +81,17 @@ public class TokenServiceImpl implements TokenService {
return optional.get(); return optional.get();
} }
@Override
@Transactional(readOnly = true)
public Token findOne(Long id) throws TokenNotFoundException {
final Optional<Token> optional = tokenRepository.findById(id);
if (optional.isEmpty()) {
log.error("Failed to find token with id {}", id);
throw new TokenNotFoundException("Failed to find token");
}
return optional.get();
}
@Override @Override
@Transactional @Transactional
public void delete(String tokenHash, Principal principal) throws TokenNotFoundException, UserNotFoundException { public void delete(String tokenHash, Principal principal) throws TokenNotFoundException, UserNotFoundException {
......
...@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; ...@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*; import lombok.*;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.Where;
import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.data.jpa.domain.support.AuditingEntityListener;
......
...@@ -263,7 +263,7 @@ export default { ...@@ -263,7 +263,7 @@ export default {
} }
}, },
login () { login () {
let redirect = ![undefined ,'/', '/login'].includes(this.$router.currentRoute.path) const redirect = ![undefined, '/', '/login'].includes(this.$router.currentRoute.path)
this.$router.push({ path: '/login', query: redirect ? { redirect: this.$router.currentRoute.path } : {} }) this.$router.push({ path: '/login', query: redirect ? { redirect: this.$router.currentRoute.path } : {} })
}, },
navigate (item) { navigate (item) {
......
...@@ -77,7 +77,8 @@ export default { ...@@ -77,7 +77,8 @@ export default {
sharedFilesystem: process.env.SHARED_FILESYSTEM || '/tmp', sharedFilesystem: process.env.SHARED_FILESYSTEM || '/tmp',
version: process.env.VERSION || 'latest', version: process.env.VERSION || 'latest',
logo: process.env.LOGO || '/logo.png', logo: process.env.LOGO || '/logo.png',
mailVerify: process.env.MAIL_VERIFY || false mailVerify: process.env.MAIL_VERIFY || false,
tokenMax: process.env.TOKEN_MAX || 5
}, },
proxy: { proxy: {
......
...@@ -9,9 +9,10 @@ ...@@ -9,9 +9,10 @@
<v-card-text> <v-card-text>
<v-list-item v-for="(item, i) in tokens" :key="i" three-line> <v-list-item v-for="(item, i) in tokens" :key="i" three-line>
<v-list-item-content> <v-list-item-content>
<v-list-item-title>sha256:{{ item.token_hash }}</v-list-item-title> <v-list-item-title :class="tokenClass(item)">sha256:{{ item.token_hash }}</v-list-item-title>
<v-list-item-subtitle v-if="!item.token"> <v-list-item-subtitle v-if="!item.token" :class="tokenClass(item)">
Created on {{ format(item.created) }}, Valid until: {{ format(item.expires) }}</v-list-item-subtitle> Last used: <span v-if="item.last_used">{{ format(item.last_used) }}</span><span v-if="!item.last_used">Never</span> &mdash; valid until: {{ format(item.expires) }}
</v-list-item-subtitle>
<v-list-item-subtitle v-if="item.token"> <v-list-item-subtitle v-if="item.token">
<v-text-field <v-text-field
v-model="item.token" v-model="item.token"
...@@ -23,12 +24,12 @@ ...@@ -23,12 +24,12 @@
@click:append-outer="copy(item)" /> @click:append-outer="copy(item)" />
</v-list-item-subtitle> </v-list-item-subtitle>
<v-list-item-subtitle v-if="!item.token"> <v-list-item-subtitle v-if="!item.token">
<a @click="revokeToken(item.token_hash)">Revoke Token</a> <a @click="revokeToken(item.id)">Revoke Token</a>
</v-list-item-subtitle> </v-list-item-subtitle>
</v-list-item-content> </v-list-item-content>
</v-list-item> </v-list-item>
<v-btn class="mt-4" x-small @click="mintToken"> <v-btn :disabled="tokens.length >= tokenMax" class="mt-4" color="secondary" small @click="mintToken">
Mint Token Create Token
</v-btn> </v-btn>
</v-card-text> </v-card-text>
</v-card> </v-card>
...@@ -59,6 +60,9 @@ export default { ...@@ -59,6 +60,9 @@ export default {
return { return {
headers: { Authorization: `Bearer ${this.token}` } headers: { Authorization: `Bearer ${this.token}` }
} }
},
tokenMax () {
return this.$config.tokenMax
} }
}, },
mounted () { mounted () {
...@@ -74,6 +78,9 @@ export default { ...@@ -74,6 +78,9 @@ export default {
format (timestamp) { format (timestamp) {
return formatTimestamp(timestamp) return formatTimestamp(timestamp)
}, },
tokenClass (token) {
return token.last_used ? '' : 'token-not_used'
},
async loadTokens () { async loadTokens () {
this.loading = true this.loading = true
try { try {
...@@ -102,10 +109,10 @@ export default { ...@@ -102,10 +109,10 @@ export default {
} }
this.loading = false this.loading = false
}, },
async revokeToken (hash) { async revokeToken (id) {
this.loading = true this.loading = true
try { try {
await this.$axios.delete(`/api/user/token/${hash}`, this.config) await this.$axios.delete(`/api/user/token/${id}`, this.config)
await this.loadTokens() await this.loadTokens()
} catch (err) { } catch (err) {
this.$toast.error('Could not delete token') this.$toast.error('Could not delete token')
...@@ -115,3 +122,8 @@ export default { ...@@ -115,3 +122,8 @@ export default {
} }
} }
</script> </script>
<style>
.token-not_used {
opacity: 0.4;
}
</style>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment