Skip to content

Commit

Permalink
Add OAuth2 Client Credentials support to venue status screen
Browse files Browse the repository at this point in the history
  • Loading branch information
KiOui committed Oct 26, 2024
1 parent c354305 commit 196f1ad
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 41 deletions.
129 changes: 91 additions & 38 deletions website/status_screen/templates/status_screen/venue_status_screen.html
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ <h2>Ready</h2>
{% endblock %}

{% block js %}
<script src="{% static 'tosti/js/base64.js' %}"></script>
<script>
const STATUS_SCREEN_CONTAINER_ID = "status-screen-container";

Expand All @@ -89,9 +90,17 @@ <h2>Ready</h2>
refreshing: false,
refreshTimer: null,
lastRefresh: null,
// Set when OAuth2 access control should be used.
client_id: null,
client_secret: null,
oauth_token: null,
}
},
created() {
// These will be set in the client if OAuth needs to be used.
this.client_id = get_cookie("TOSTI_CLIENT_ID");
this.client_secret = get_cookie("TOSTI_CLIENT_SECRET");

this.refresh();
this.recalculate_progress_interval = setInterval(this.updateTrackProgress, 100);
document.addEventListener("visibilitychange", this.visibilityChange);
Expand All @@ -118,6 +127,44 @@ <h2>Ready</h2>
},
},
methods: {
get_oauth_token() {
return new Promise((resolve, reject) => {
if (this.oauth_token !== null) {
let current_time = new Date().getTime();
if (current_time < this.oauth_token.expires_at) {
resolve(this.oauth_token.access_token);
return;
}
}

get_oauth_token_with_client_credentials(this.client_id, this.client_secret)
.then(data => {
this.oauth_token = data;
resolve(this.oauth_token.access_token);
});
});
},
get_oauth_header() {
return new Promise((resolve, reject) => {
if (this.should_use_token()) {
return this.get_oauth_token()
.then(token => {
if (token === null) {
resolve(null);
} else {
resolve({
"Authorization": "Bearer " + token
});
}
});
} else {
resolve(null);
}
});
},
should_use_token() {
return this.client_id !== null && this.client_secret !== null;
},
map_orders_by_user(orders) {
let orders_mapped_user = {};
for (let i = 0; i < orders.length; i++) {
Expand Down Expand Up @@ -151,52 +198,58 @@ <h2>Ready</h2>
if (this.refreshing) {
return;
}

clearTimeout(this.refreshTimer);
this.refreshing = true;

return new Promise((resolve, reject) => {
fetch(
"{% url 'v1:orders_listcreate' shift=shift %}?type=0&picked_up=false&ordering=ready_at",
{
method: 'GET',
headers: {
"X-CSRFToken": get_csrf_token(),
"Accept": 'application/json',
}
this.get_oauth_header().then(header => {
if (header === null) {
header = {};
}
).then(response => {
if (response.status === 200) {
return response.json();
} else {
reject(response);
}
}).then(data => {
this.orders = data;
}).catch(error => {
reject(`An error occurred while refreshing orders for shift {{ shift.id }}. Error: ${error}`)
})

fetch(
"{% url 'v1:ordervenues_activeshift' order_venue=order_venue %}",
{
method: 'GET',
headers: {
"X-CSRFToken": get_csrf_token(),
"Accept": 'application/json',
fetch(
"{% url 'v1:orders_listcreate' shift=shift %}?type=0&picked_up=false&ordering=ready_at",
{
method: 'GET',
headers: Object.assign({}, header, {
"X-CSRFToken": get_csrf_token(),
"Accept": 'application/json',
}),
}
).then(response => {
if (response.status === 200) {
return response.json();
} else {
reject(response);
}
}).then(data => {
this.orders = data;
}).catch(error => {
reject(`An error occurred while refreshing orders for shift {{ shift.id }}. Error: ${error}`)
})

fetch(
"{% url 'v1:ordervenues_activeshift' order_venue=order_venue %}",
{
method: 'GET',
headers: Object.assign({}, header, {
"X-CSRFToken": get_csrf_token(),
"Accept": 'application/json',
}),
}
).then(response => {
/* We want to change the page to the music page again when the shift becomes inactive
So if the response on the active shift api call is not 200 we reload the page
*/
if (response.status === 404) {
location.reload();
}
}
).then(response => {
/* We want to change the page to the music page again when the shift becomes inactive
So if the response on the active shift api call is not 200 we reload the page
*/
if (response.status !== 200) {
location.reload();
}

resolve();
}).catch(error => {
reject(`An error occurred while refreshing orders for venue {{ order_venue.name }}. Error: ${error}`)
resolve();
}).catch(error => {
reject(`An error occurred while refreshing orders for venue {{ order_venue.name }}. Error: ${error}`)
})
})
})
.catch(error => {
Expand Down
12 changes: 9 additions & 3 deletions website/status_screen/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,15 @@ def get(self, request, **kwargs):
order_venue = kwargs.get("order_venue")
active_shift = currently_active_shift_for_venue(order_venue)

player = Player.objects.get(venue=order_venue.venue)
try:
player = Player.objects.get(venue=order_venue.venue)
except Player.DoesNotExist:
# No player configured for the venue.
player = None

if active_shift:
if active_shift is not None:
return render(request, self.orders_template_name, {"shift": active_shift, "order_venue": order_venue})
else:
elif player is not None:
return render(request, self.music_template_name, {"player": player, "order_venue": order_venue})
else:
raise Http404()
32 changes: 32 additions & 0 deletions website/tosti/static/tosti/js/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,5 +162,37 @@ function formatPrice(priceToFormat) {
}).format(priceToFormat).trim();
}

function get_oauth_token_with_client_credentials(client_id, client_secret) {
let credentials = client_id + ":" + client_secret;
let credentials_utf8 = strToUTF8Arr(credentials);
let credentials_base64 = base64EncArr(credentials_utf8);
return fetch(
"/oauth/token/",
{
method: 'POST',
headers: {
"Authorization": "Basic " + credentials_base64,
"Content-Type": "application/x-www-form-urlencoded",
"Accept": 'application/json',
},
body: "grant_type=client_credentials"
}
).then(response => {
if (response.status === 200) {
return response.json();
} else {
reject(response);
}
}).then(data => {
let expires_in = data.expires_in;
let current_time = new Date().getTime();
data.expires_at = current_time + (expires_in * 1000);
return data;
}).catch(error => {
console.log(`An error occurred while retrieving an access token. Error: ${error}`);
return null;
});
}

update_refresh_list();
document.addEventListener("visibilitychange", visibilityChange);
165 changes: 165 additions & 0 deletions website/tosti/static/tosti/js/base64.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
function b64ToUint6 (nChr) {

return nChr > 64 && nChr < 91 ?
nChr - 65
: nChr > 96 && nChr < 123 ?
nChr - 71
: nChr > 47 && nChr < 58 ?
nChr + 4
: nChr === 43 ?
62
: nChr === 47 ?
63
:
0;

}

function base64DecToArr (sBase64, nBlockSize) {

var
sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""), nInLen = sB64Enc.length,
nOutLen = nBlockSize ? Math.ceil((nInLen * 3 + 1 >>> 2) / nBlockSize) * nBlockSize : nInLen * 3 + 1 >>> 2, aBytes = new Uint8Array(nOutLen);

for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
aBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
}
nUint24 = 0;
}
}

return aBytes;
}

/* Base64 string to array encoding */

function uint6ToB64 (nUint6) {

return nUint6 < 26 ?
nUint6 + 65
: nUint6 < 52 ?
nUint6 + 71
: nUint6 < 62 ?
nUint6 - 4
: nUint6 === 62 ?
43
: nUint6 === 63 ?
47
:
65;

}

function base64EncArr (aBytes) {

var eqLen = (3 - (aBytes.length % 3)) % 3, sB64Enc = "";

for (var nMod3, nLen = aBytes.length, nUint24 = 0, nIdx = 0; nIdx < nLen; nIdx++) {
nMod3 = nIdx % 3;
/* Uncomment the following line in order to split the output in lines 76-character long: */
/*
if (nIdx > 0 && (nIdx * 4 / 3) % 76 === 0) { sB64Enc += "\r\n"; }
*/
nUint24 |= aBytes[nIdx] << (16 >>> nMod3 & 24);
if (nMod3 === 2 || aBytes.length - nIdx === 1) {
sB64Enc += String.fromCharCode(uint6ToB64(nUint24 >>> 18 & 63), uint6ToB64(nUint24 >>> 12 & 63), uint6ToB64(nUint24 >>> 6 & 63), uint6ToB64(nUint24 & 63));
nUint24 = 0;
}
}

return eqLen === 0 ?
sB64Enc
:
sB64Enc.substring(0, sB64Enc.length - eqLen) + (eqLen === 1 ? "=" : "==");

}

/* UTF-8 array to DOMString and vice versa */

function UTF8ArrToStr (aBytes) {

var sView = "";

for (var nPart, nLen = aBytes.length, nIdx = 0; nIdx < nLen; nIdx++) {
nPart = aBytes[nIdx];
sView += String.fromCharCode(
nPart > 251 && nPart < 254 && nIdx + 5 < nLen ? /* six bytes */
/* (nPart - 252 << 30) may be not so safe in ECMAScript! So...: */
(nPart - 252) * 1073741824 + (aBytes[++nIdx] - 128 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
: nPart > 247 && nPart < 252 && nIdx + 4 < nLen ? /* five bytes */
(nPart - 248 << 24) + (aBytes[++nIdx] - 128 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
: nPart > 239 && nPart < 248 && nIdx + 3 < nLen ? /* four bytes */
(nPart - 240 << 18) + (aBytes[++nIdx] - 128 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
: nPart > 223 && nPart < 240 && nIdx + 2 < nLen ? /* three bytes */
(nPart - 224 << 12) + (aBytes[++nIdx] - 128 << 6) + aBytes[++nIdx] - 128
: nPart > 191 && nPart < 224 && nIdx + 1 < nLen ? /* two bytes */
(nPart - 192 << 6) + aBytes[++nIdx] - 128
: /* nPart < 127 ? */ /* one byte */
nPart
);
}

return sView;

}

function strToUTF8Arr (sDOMStr) {

var aBytes, nChr, nStrLen = sDOMStr.length, nArrLen = 0;

/* mapping... */

for (var nMapIdx = 0; nMapIdx < nStrLen; nMapIdx++) {
nChr = sDOMStr.charCodeAt(nMapIdx);
nArrLen += nChr < 0x80 ? 1 : nChr < 0x800 ? 2 : nChr < 0x10000 ? 3 : nChr < 0x200000 ? 4 : nChr < 0x4000000 ? 5 : 6;
}

aBytes = new Uint8Array(nArrLen);

/* transcription... */

for (var nIdx = 0, nChrIdx = 0; nIdx < nArrLen; nChrIdx++) {
nChr = sDOMStr.charCodeAt(nChrIdx);
if (nChr < 128) {
/* one byte */
aBytes[nIdx++] = nChr;
} else if (nChr < 0x800) {
/* two bytes */
aBytes[nIdx++] = 192 + (nChr >>> 6);
aBytes[nIdx++] = 128 + (nChr & 63);
} else if (nChr < 0x10000) {
/* three bytes */
aBytes[nIdx++] = 224 + (nChr >>> 12);
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
aBytes[nIdx++] = 128 + (nChr & 63);
} else if (nChr < 0x200000) {
/* four bytes */
aBytes[nIdx++] = 240 + (nChr >>> 18);
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
aBytes[nIdx++] = 128 + (nChr & 63);
} else if (nChr < 0x4000000) {
/* five bytes */
aBytes[nIdx++] = 248 + (nChr >>> 24);
aBytes[nIdx++] = 128 + (nChr >>> 18 & 63);
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
aBytes[nIdx++] = 128 + (nChr & 63);
} else /* if (nChr <= 0x7fffffff) */ {
/* six bytes */
aBytes[nIdx++] = 252 + (nChr >>> 30);
aBytes[nIdx++] = 128 + (nChr >>> 24 & 63);
aBytes[nIdx++] = 128 + (nChr >>> 18 & 63);
aBytes[nIdx++] = 128 + (nChr >>> 12 & 63);
aBytes[nIdx++] = 128 + (nChr >>> 6 & 63);
aBytes[nIdx++] = 128 + (nChr & 63);
}
}

return aBytes;

}

0 comments on commit 196f1ad

Please sign in to comment.