-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit df78fe7
Showing
4 changed files
with
281 additions
and
0 deletions.
There are no files selected for viewing
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
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> |
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
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() | ||
} | ||
}) |
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
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" | ||
} | ||
} |
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
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> |