Skip to content

Commit

Permalink
It works.
Browse files Browse the repository at this point in the history
  • Loading branch information
abrasive committed Apr 11, 2019
0 parents commit df78fe7
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 0 deletions.
44 changes: 44 additions & 0 deletions instructions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!doctype html>
<html>
<head>
<style>
div {
width: 400px;
margin: 20px;
}
</style>
</head>
<body>
<div>
<h1>myGov TOTP enroller</h1>

This tool will get you a TOTP key (as a QR code) for logging in to myGov.
<p>

You then need to use your TOTP authenticator to log in, every time, unless you disable it after logging in.
<p>

After you log in to myGov, you will be shown the QR code.
You need to enroll this in your authenticator app - or maybe more than one, if you want a backup.
Then, enter the current generated code to activate it.
<p>

The TOTP access method will not be activated unless you enter a correct key.
This also means you won't be locked out if something goes wrong in the process.
<p>

To get started, just click the button.

<form action="https://auth.my.gov.au/mga/sps/oauth/oauth20/authorize" method="get">
<input type="hidden" name="response_type" value="code" />
<input type="hidden" name="state" value="63065293C60AD6A479B67F1DFC79BBC75DEF04C3" />
<input type="hidden" name="client_id" value="g2c2pjLUThOaBumECqbf" />
<input type="hidden" name="scope" value="totp" />
<input type="hidden" name="redirect_uri" value="au.gov.my://app" />
Phone name shown in myGov: <input type="text" name="device_name" value="SM-N950F" /><br>
Phone type shown in myGov: <input type="text" name="device_type" value="samsung SM-N950F" /><br>
<input type="submit" />
</form>
</div>
</body>
</html>
169 changes: 169 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
const { app, protocol, BrowserWindow, ipcMain } = require('electron');
const url = require('url');
const request = require('request');
const qrcode = require('qrcode');
const { base32, base64 } = require('rfc4648');
const prompt = require('electron-prompt');

let win
let client_id='g2c2pjLUThOaBumECqbf'
let token

function setStatus(stat) {
win.webContents.send("status", stat);
}
function setDetail(detail) {
win.webContents.send("detail", detail);
}
function setError(headline, detail) {
setStatus("ERROR: " + headline);
setDetail(detail);
}
function showCodeForm(on) {
if (on) {
win.webContents.send("showform", "block");
} else {
win.webContents.send("showform", "none");
}
}

function verifyCode(code) {
const options = {
method: 'POST',
url: 'https://api.my.gov.au/authbiz-ext-sec/api/v1/authclients/g2c2pjLUThOaBumECqbf/totpverify.json',
headers: {
'content-type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: {
password: code,
},
json: true,
};

request(options, function (error, response, body) {
if (error) {
setError("Code verification failed, try again", error);
return;
}
if (body == null) { // lol
setStatus("Enrollment complete! DON'T LOSE THE CODE");
showCodeForm(false);
return;
} else if (body.error) {
setError("Code verification failed, try again", body.error + ' - ' + body.error_description);
return;
}
});
}

function makeQR(secret) {
secret32 = base32.stringify(base64.parse(secret))
secret32 = secret32.replace(/=+$/, "")
totp_uri = 'otpauth://totp/myGov?secret=' + secret32 + '&algorithm=SHA512'

qrcode.toDataURL(totp_uri)
.then(url => {
setStatus("Enroll this secret in your TOTP client and enter the current code");
setDetail(totp_uri + '<p><img src="' + url + '"/>');
showCodeForm(true);
})
.catch(err => {
setError("QR encoding error", err);
})
}

function requestSecret(token) {
setStatus("Requesting OAuth secret...");
const options = {
method: 'POST',
url: 'https://api.my.gov.au/authbiz-ext-sec/api/v1/authclients/g2c2pjLUThOaBumECqbf/totpcredential.json',
headers: {'Authorization': 'Bearer ' + token},
};
request(options, function (error, response, body) {
if (error) {
setError("Secret request failed", error);
return;
}
body = JSON.parse(body)
if (body.error) {
setError("Secret request failed",
body.error + ' - ' + body.error_description);
return;
}

secret = body.secret;
makeQR(secret);
});
}

function requestToken(code) {
setStatus("Requesting OAuth token...");

const options = {
method: 'POST',
url: 'https://auth.my.gov.au/mga/sps/oauth/oauth20/token',
form: {
client_id: client_id,
redirect_uri: 'au.gov.my://app',
grant_type: 'authorization_code',
code: code,
},
};

request(options, function (error, response, body) {
if (error) {
setError("Token request failed", error);
return;
}
body = JSON.parse(body)
if (body.error) {
setError("Token request failed",
body.error + ' - ' + body.error_description);
return;
}

token = body.access_token;

requestSecret(token);
});
}

function createWindow () {
protocol.registerFileProtocol('au.gov.my', (req, callback) => {
protocol.unregisterProtocol('au.gov.my'); // otherwise background requests can trigger this again!

code = url.parse(req.url, true).query.code
callback({ path: `${__dirname}/ui.html` })

requestToken(code)
}, (error) => {
if (error) console.error('Failed to register protocol')
})

win = new BrowserWindow({ width: 800, height: 600 })

win.loadURL(`file://${__dirname}/instructions.html`)

ipcMain.on('code', (event, arg) => {
verifyCode(arg);
});

win.on('closed', () => {
win = null
})
}

app.on('ready', createWindow)

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})

app.on('activate', () => {
if (win === null) {
createWindow()
}
})
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "mygov-totp-enroll",
"version": "0.1.0",
"description": "Get a TOTP secret from my.gov.au which you can use for proper 2FA login.",
"repository": {
"type": "git",
"url": "https://github.com/abrasive/mygov-totp-enroll.git"
},
"license": "WTFPL",
"main": "main.js",
"scripts": {
"start": "electron ."
},
"Dependencies": {
"electron": "^4.1.4",
"qrcode": "^1.3.3",
"request": "^2.88.0",
"rfc4648": "^1.2.0"
}
}
48 changes: 48 additions & 0 deletions ui.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!doctype html>
<html>
<head>
<style>
div {
display: flex;
flex-direction: column;
align-items: center;
word-break: break-all;
}
#detail {
width: 400px;
}
</style>
</head>
<body>
<div>
<h1 id="status">Loading...</h1>
<div id="detail"></div>

<form id="form">
<input type="number" maxlength=6 id="code" />
<input type="submit" />
</form>
</div>

<script>
document.getElementById("form").style.display = "none";

const { ipcRenderer } = require('electron');
ipcRenderer.on('status', (event, arg) => {
document.getElementById("status").innerHTML = arg;
})
ipcRenderer.on('detail', (event, arg) => {
document.getElementById("detail").innerHTML = arg;
})

ipcRenderer.on('showform', (event, arg) => {
document.getElementById("form").style.display = arg;
})

document.getElementById("form").onsubmit = function() {
code = document.getElementById("code").value;
ipcRenderer.send('code', code);
}
</script>
</body>
</html>

0 comments on commit df78fe7

Please sign in to comment.