mirror of
https://github.com/Luzifer/vault-otp-ui.git
synced 2024-11-08 08:10:11 +00:00
Implement pre-fetching for next codes
to provide a more seamless experience Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
c120d8864d
commit
34214190f8
5 changed files with 326 additions and 179 deletions
|
@ -1,5 +1,12 @@
|
|||
currentTimeout = 0
|
||||
clipboard = undefined
|
||||
clipboard = null
|
||||
preFetch = null
|
||||
|
||||
fetchInProgress = false
|
||||
serverConnectionError = false
|
||||
|
||||
iterationCurrent = 'current'
|
||||
iterationNext = 'next'
|
||||
|
||||
# document-ready function to start Javascript processing
|
||||
$ ->
|
||||
|
@ -38,20 +45,38 @@ delay = (delayMSecs, fkt) ->
|
|||
window.setTimeout fkt, delayMSecs
|
||||
|
||||
# fetchCodes contacts the backend to receive JSON containing current codes
|
||||
fetchCodes = () ->
|
||||
fetchCodes = (iteration) ->
|
||||
if fetchInProgress
|
||||
return
|
||||
|
||||
fetchInProgress = true
|
||||
|
||||
if iteration == iterationCurrent
|
||||
successFunc = updateCodes
|
||||
else
|
||||
successFunc = updatePreFetch
|
||||
|
||||
if iteration == iterationCurrent and preFetch != null
|
||||
data = preFetch
|
||||
preFetch = null
|
||||
successFunc data
|
||||
return
|
||||
|
||||
$.ajax
|
||||
url: 'codes.json',
|
||||
success: updateCodes,
|
||||
url: "codes.json?it=#{iteration}",
|
||||
success: successFunc,
|
||||
dataType: 'json',
|
||||
error: () ->
|
||||
createAlert 'danger', 'Oops.', 'Server could not be contacted. Maybe you (or the server) are offline? I will retry in a few seconds.', 5000
|
||||
delay 5000, fetchCodes
|
||||
fetchInProgress = false
|
||||
createAlert 'danger', 'Oops.', 'Server could not be contacted. Maybe you (or the server) are offline? Reload to try again.', 0
|
||||
serverConnectionError = true
|
||||
statusCode:
|
||||
401: () ->
|
||||
window.location.reload()
|
||||
500: () ->
|
||||
createAlert 'danger', 'Oops.', 'The server responded with an internal error. I will retry in a few seconds.', 2000
|
||||
delay 2000, fetchCodes
|
||||
fetchInProgress = false
|
||||
createAlert 'danger', 'Oops.', 'The server responded with an internal error. Reload to try again.', 0
|
||||
serverConnectionError = true
|
||||
|
||||
# filterChange is called when changing the filter field and matches the
|
||||
# titles of all shown entries. Those not matching the given regular expression
|
||||
|
@ -71,7 +96,7 @@ initializeApplication = () ->
|
|||
$('#keylist').empty()
|
||||
$('#filter').bind 'keyup', filterChange
|
||||
tick 500, refreshTimerProgress
|
||||
fetchCodes()
|
||||
fetchCodes iterationCurrent
|
||||
|
||||
# refreshTimerProgress updates the top progressbar to display the
|
||||
# remaining time until the one-time-passwords changes
|
||||
|
@ -79,6 +104,10 @@ refreshTimerProgress = () ->
|
|||
secondsLeft = timeLeft()
|
||||
$('#timer').css 'width', "#{secondsLeft / 30 * 100}%"
|
||||
|
||||
if secondsLeft < 10 and preFetch == null and not serverConnectionError
|
||||
# Do a pre-fetch to provide a seamless experience
|
||||
fetchCodes iterationNext
|
||||
|
||||
# tick is a convenience wrapper to swap parameters of setInterval
|
||||
tick = (delay, fkt) ->
|
||||
window.setInterval fkt, delay
|
||||
|
@ -108,4 +137,10 @@ updateCodes = (data) ->
|
|||
|
||||
filterChange()
|
||||
|
||||
delay timeLeft()*1000, fetchCodes
|
||||
delay timeLeft()*1000, ->
|
||||
fetchCodes iterationCurrent
|
||||
fetchInProgress = false
|
||||
|
||||
updatePreFetch = (data) ->
|
||||
preFetch = data
|
||||
fetchInProgress = false
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
// Generated by CoffeeScript 1.12.4
|
||||
(function() {
|
||||
var clipboard, createAlert, createOTPItem, currentTimeout, delay, fetchCodes, filterChange, initializeApplication, refreshTimerProgress, tick, timeLeft, updateCodes;
|
||||
var clipboard, createAlert, createOTPItem, currentTimeout, delay, fetchCodes, fetchInProgress, filterChange, initializeApplication, iterationCurrent, iterationNext, preFetch, refreshTimerProgress, serverConnectionError, tick, timeLeft, updateCodes, updatePreFetch;
|
||||
|
||||
currentTimeout = 0;
|
||||
|
||||
clipboard = void 0;
|
||||
clipboard = null;
|
||||
|
||||
preFetch = null;
|
||||
|
||||
fetchInProgress = false;
|
||||
|
||||
serverConnectionError = false;
|
||||
|
||||
iterationCurrent = 'current';
|
||||
|
||||
iterationNext = 'next';
|
||||
|
||||
$(function() {
|
||||
if ($('body').hasClass('state-signedin')) {
|
||||
|
@ -41,22 +51,40 @@
|
|||
return window.setTimeout(fkt, delayMSecs);
|
||||
};
|
||||
|
||||
fetchCodes = function() {
|
||||
fetchCodes = function(iteration) {
|
||||
var data, successFunc;
|
||||
if (fetchInProgress) {
|
||||
return;
|
||||
}
|
||||
fetchInProgress = true;
|
||||
if (iteration === iterationCurrent) {
|
||||
successFunc = updateCodes;
|
||||
} else {
|
||||
successFunc = updatePreFetch;
|
||||
}
|
||||
if (iteration === iterationCurrent && preFetch !== null) {
|
||||
data = preFetch;
|
||||
preFetch = null;
|
||||
successFunc(data);
|
||||
return;
|
||||
}
|
||||
return $.ajax({
|
||||
url: 'codes.json',
|
||||
success: updateCodes,
|
||||
url: "codes.json?it=" + iteration,
|
||||
success: successFunc,
|
||||
dataType: 'json',
|
||||
error: function() {
|
||||
createAlert('danger', 'Oops.', 'Server could not be contacted. Maybe you (or the server) are offline? I will retry in a few seconds.', 5000);
|
||||
return delay(5000, fetchCodes);
|
||||
fetchInProgress = false;
|
||||
createAlert('danger', 'Oops.', 'Server could not be contacted. Maybe you (or the server) are offline? Reload to try again.', 0);
|
||||
return serverConnectionError = true;
|
||||
},
|
||||
statusCode: {
|
||||
401: function() {
|
||||
return window.location.reload();
|
||||
},
|
||||
500: function() {
|
||||
createAlert('danger', 'Oops.', 'The server responded with an internal error. I will retry in a few seconds.', 2000);
|
||||
return delay(2000, fetchCodes);
|
||||
fetchInProgress = false;
|
||||
createAlert('danger', 'Oops.', 'The server responded with an internal error. Reload to try again.', 0);
|
||||
return serverConnectionError = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -78,13 +106,16 @@
|
|||
$('#keylist').empty();
|
||||
$('#filter').bind('keyup', filterChange);
|
||||
tick(500, refreshTimerProgress);
|
||||
return fetchCodes();
|
||||
return fetchCodes(iterationCurrent);
|
||||
};
|
||||
|
||||
refreshTimerProgress = function() {
|
||||
var secondsLeft;
|
||||
secondsLeft = timeLeft();
|
||||
return $('#timer').css('width', (secondsLeft / 30 * 100) + "%");
|
||||
$('#timer').css('width', (secondsLeft / 30 * 100) + "%");
|
||||
if (secondsLeft < 10 && preFetch === null && !serverConnectionError) {
|
||||
return fetchCodes(iterationNext);
|
||||
}
|
||||
};
|
||||
|
||||
tick = function(delay, fkt) {
|
||||
|
@ -115,7 +146,15 @@
|
|||
}
|
||||
});
|
||||
filterChange();
|
||||
return delay(timeLeft() * 1000, fetchCodes);
|
||||
delay(timeLeft() * 1000, function() {
|
||||
return fetchCodes(iterationCurrent);
|
||||
});
|
||||
return fetchInProgress = false;
|
||||
};
|
||||
|
||||
updatePreFetch = function(data) {
|
||||
preFetch = data;
|
||||
return fetchInProgress = false;
|
||||
};
|
||||
|
||||
}).call(this);
|
||||
|
|
369
assets.go
369
assets.go
File diff suppressed because one or more lines are too long
10
main.go
10
main.go
|
@ -207,7 +207,12 @@ func handleCodesJSON(res http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
log.WithFields(log.Fields{"token": hashSecret(tok)}).Debugf("Checked / renewed token")
|
||||
|
||||
tokens, err := getSecretsFromVault(tok)
|
||||
pointOfTime := time.Now()
|
||||
if r.URL.Query().Get("it") == "next" {
|
||||
pointOfTime = pointOfTime.Add(time.Duration(30-(pointOfTime.Second()%30)) * time.Second)
|
||||
}
|
||||
|
||||
tokens, err := getSecretsFromVault(tok, pointOfTime)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to fetch codes: %s", err)
|
||||
http.Error(res, `{"error":"Unexpected error while fetching tokens"}`, http.StatusInternalServerError)
|
||||
|
@ -221,13 +226,12 @@ func handleCodesJSON(res http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
n := time.Now()
|
||||
result := struct {
|
||||
Tokens []*token `json:"tokens"`
|
||||
NextWrap time.Time `json:"next_wrap"`
|
||||
}{
|
||||
Tokens: tokens,
|
||||
NextWrap: n.Add(time.Duration(30-(n.Second()%30)) * time.Second),
|
||||
NextWrap: pointOfTime.Add(time.Duration(30-(pointOfTime.Second()%30)) * time.Second),
|
||||
}
|
||||
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
|
|
8
token.go
8
token.go
|
@ -79,7 +79,7 @@ func useOrRenewToken(tok, accessToken string) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func getSecretsFromVault(tok string) ([]*token, error) {
|
||||
func getSecretsFromVault(tok string, pointOfTime time.Time) ([]*token, error) {
|
||||
client, err := api.NewClient(&api.Config{
|
||||
Address: cfg.Vault.Address,
|
||||
})
|
||||
|
@ -112,7 +112,7 @@ func getSecretsFromVault(tok string) ([]*token, error) {
|
|||
case key := <-scanPool:
|
||||
go scanKeyForSubKeys(client, key, scanPool, keyPoolChan, wg)
|
||||
case key := <-keyPoolChan:
|
||||
go fetchTokenFromKey(client, key, respChan, wg)
|
||||
go fetchTokenFromKey(client, key, respChan, wg, pointOfTime)
|
||||
case t := <-respChan:
|
||||
resp = append(resp, t)
|
||||
case <-done:
|
||||
|
@ -159,7 +159,7 @@ func scanKeyForSubKeys(client *api.Client, key string, subKeyChan, tokenKeyChan
|
|||
}
|
||||
}
|
||||
|
||||
func fetchTokenFromKey(client *api.Client, k string, respChan chan *token, wg *sync.WaitGroup) {
|
||||
func fetchTokenFromKey(client *api.Client, k string, respChan chan *token, wg *sync.WaitGroup, pointOfTime time.Time) {
|
||||
defer wg.Done()
|
||||
|
||||
data, err := client.Logical().Read(k)
|
||||
|
@ -182,7 +182,7 @@ func fetchTokenFromKey(client *api.Client, k string, respChan chan *token, wg *s
|
|||
switch k {
|
||||
case cfg.Vault.SecretField:
|
||||
tok.Secret = v.(string)
|
||||
tok.GenerateCode(time.Now())
|
||||
tok.GenerateCode(pointOfTime)
|
||||
case "code":
|
||||
tok.Code = v.(string)
|
||||
case "name":
|
||||
|
|
Loading…
Reference in a new issue