Created
July 24, 2024 20:42
-
-
Save RobinBoers/f57164c20f05bafa3f847910c5d784e4 to your computer and use it in GitHub Desktop.
Script for obtaining Somtoday access token.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env -S bun | |
import crypto from "node:crypto"; | |
import puppeteer from "puppeteer"; | |
const RANDOM_BYTES = 32; | |
const CLIENT_ID = "D50E0C06-32D1-4B41-A137-A9A850C892C2"; | |
const SCOPE = "openid"; | |
const APP = "somtodayleerling" | |
const ENDPOINT = "https://inloggen.somtoday.nl/oauth2"; | |
const REDIRECT_URI = `${APP}://oauth/callback`; | |
const STATE = "boobswow"; | |
// TODO(robin): automate this. | |
// See https://servers.somtoday.nl/organisaties.json | |
const TENANT_ID = "ec284fda-3d0f-4f54-a77e-91d94b94ff1a"; | |
let params; | |
function base64_url_encode(buffer: Buffer): string { | |
return buffer.toString('base64') | |
.replace(/\+/g, '-') | |
.replace(/\//g, '_') | |
.replace(/=/g, ''); | |
} | |
function sha256sum(buffer: Buffer | string): Buffer { | |
return crypto.createHash("sha256").update(buffer).digest(); | |
} | |
const username = prompt("Student ID:"); | |
const password = prompt("Password:"); | |
if(!username || !password) { | |
process.stderr.write("You wanna log in or what?\n"); | |
process.exit(1); | |
} | |
const code_verifier = base64_url_encode(crypto.randomBytes(RANDOM_BYTES)); | |
const code_challenge = base64_url_encode(sha256sum(code_verifier)); | |
params = new URLSearchParams({ | |
response_type: "code", | |
prompt: "login", | |
client_id: CLIENT_ID, | |
tenant_uuid: TENANT_ID, | |
redirect_uri: REDIRECT_URI, | |
scope: SCOPE, | |
code_challenge: code_challenge, | |
code_challenge_method: "S256", | |
state: STATE, | |
session: "no_session" | |
}); | |
const browser = await puppeteer.launch(); | |
const page = await browser.newPage(); | |
let code: string | null = null; | |
let processed = false; | |
await page.on("response", (response) => { | |
const location = response.headers()["location"]; | |
if(!location || !location.startsWith(APP)) return; | |
const url = new URL(location); | |
processed = true; | |
code = url.searchParams.get("code"); | |
}); | |
const url = `${ENDPOINT}/authorize?${params.toString()}`; | |
await page.goto(url); | |
await page.type("#usernameField", username); | |
await page.click("a[type='submit']"); | |
await page.type("#passwordField", password); | |
// Submit the final page, triggering a redirect to REDIRECT_URI | |
await page.click("a[type='submit']"); | |
while(!processed) { | |
await Bun.sleep(5000); | |
process.stdout.write("Waiting for the code... (it's taking a while)\n"); | |
} | |
await browser.close(); | |
if(!code) { | |
process.stderr.write("Could not find code. Sowwy!\n"); | |
process.exit(1); | |
} | |
const response = await fetch(`${ENDPOINT}/token`, { | |
method: "POST", | |
body: new URLSearchParams({ | |
grant_type: "authorization_code", | |
code: code, | |
code_verifier: code_verifier, | |
client_id: CLIENT_ID, | |
redirect_uri: REDIRECT_URI, | |
scope: SCOPE, | |
session: "no_session" | |
}) | |
}); | |
if(!response.ok) { | |
process.stderr.write("Failed to obtain access token. Got:\n"); | |
console.log(response); | |
} | |
const body = await response.text() | |
process.stdout.write(body); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment