101 lines
4.2 KiB
Plaintext

#load "../../lib/app.uce"
COMPONENT(Request& context)
{
String title = first(context.props["title"].to_string(), "Sign in with OAuth");
String subtitle = first(context.props["subtitle"].to_string(), "Choose your preferred authentication method");
String callback_url = first(context.props["callback_url"].to_string(), app_link("auth/callback", context));
DValue services = context.props["services"];
if(services.get_type_name() != "array" || services["google"]["name"].to_string() == "")
{
services["google"]["name"] = "Google";
services["google"]["color"] = "#4285f4";
services["google"]["icon"] = "fab fa-google";
services["google"]["scope"] = "openid email profile";
services["google"]["auth_url"] = "https://accounts.google.com/o/oauth2/v2/auth";
services["github"]["name"] = "GitHub";
services["github"]["color"] = "#333333";
services["github"]["icon"] = "fab fa-github";
services["github"]["scope"] = "user:email";
services["github"]["auth_url"] = "https://github.com/login/oauth/authorize";
services["discord"]["name"] = "Discord";
services["discord"]["color"] = "#5865f2";
services["discord"]["icon"] = "fab fa-discord";
services["discord"]["scope"] = "identify email";
services["discord"]["auth_url"] = "https://discord.com/api/oauth2/authorize";
services["twitch"]["name"] = "Twitch";
services["twitch"]["color"] = "#9146ff";
services["twitch"]["icon"] = "fab fa-twitch";
services["twitch"]["scope"] = "user:read:email";
services["twitch"]["auth_url"] = "https://id.twitch.tv/oauth2/authorize";
}
<>
<form class="card">
<div>
<h2><?= title ?></h2>
<label><?= subtitle ?></label>
</div>
<? services.each([&](DValue service, String service_key) {
String client_id = context.props[service_key + "_client_id"].to_string();
String handler_name = ascii_safe_name(service_key);
String service_name = service["name"].to_string();
String scope = service["scope"].to_string();
String auth_url = service["auth_url"].to_string();
String store_url = app_link("auth/store-oauth-session", context);
?><div data-service="<?= service_key ?>">
<button type="button" class="btn" onclick='initiateOAuth_<?= handler_name ?>()'>
<i class="<?= service["icon"].to_string() ?>" style="color: <?= service["color"].to_string() ?>"></i>
Continue with <?= service_name ?>
</button>
<div class="loading" style="display: none;"><span>Connecting...</span></div>
<label>Secure authentication via <?= service_name ?></label>
<script>
window.initiateOAuth_<?= handler_name ?> = function () {
const wrapper = document.querySelector('[data-service="<?= service_key ?>"]');
if (!wrapper) return;
const button = wrapper.querySelector('.btn');
const loading = wrapper.querySelector('.loading');
if (button) button.style.display = 'none';
if (loading) loading.style.display = 'block';
const clientId = <?: json_encode(client_id) ?>;
if (!clientId || clientId.indexOf('YOUR_') === 0) {
alert('<?= service_name ?> OAuth is not configured yet.');
if (button) button.style.display = '';
if (loading) loading.style.display = 'none';
return;
}
const state = btoa(Math.random().toString(36).substring(2) + Math.random().toString(36).substring(2)).replace(/[^a-zA-Z0-9]/g, '');
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: <?: json_encode(callback_url) ?>,
scope: <?: json_encode(scope) ?>,
response_type: 'code',
state: state
});
sessionStorage.setItem('oauth_state', state);
sessionStorage.setItem('oauth_service', <?: json_encode(service_key) ?>);
fetch(<?: json_encode(store_url) ?>, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({oauth_service: <?: json_encode(service_key) ?>, oauth_state: state})
}).catch(console.error).finally(() => {
window.location.href = <?: json_encode(auth_url) ?> + '?' + params.toString();
});
};
</script>
</div><?
}); ?>
<div class="banner" id="oauth-status" style="display: none;"></div>
</form>
</>
}