119 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			119 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 'use strict'; | ||
|  | 
 | ||
|  | var Keypairs = require('@root/keypairs'); | ||
|  | var PocketId = module.exports; | ||
|  | var request = require('./request.js'); | ||
|  | 
 | ||
|  | var keyJson = window.localStorage.getItem('private.jwk.json'); | ||
|  | 
 | ||
|  | PocketId.signIdToken = async function (idToken) { | ||
|  | 	var pair = await Keypairs.parseOrGenerate({ key: keyJson }); | ||
|  | 	var jwt = await Keypairs.signJwt({ | ||
|  | 		jwk: pair.private, | ||
|  | 		iss: window.location.protocol + '//' + window.location.hostname, | ||
|  | 		exp: '15m', | ||
|  | 		claims: { | ||
|  | 			contact: ['google:' + idToken] | ||
|  | 		} | ||
|  | 	}); | ||
|  | 	return jwt; | ||
|  | }; | ||
|  | 
 | ||
|  | PocketId.auth = {}; | ||
|  | PocketId.auth.meta = async function ({ email }) { | ||
|  | 	var loc = window.location; | ||
|  | 	var body = await request({ | ||
|  | 		method: 'GET', | ||
|  | 		url: | ||
|  | 			loc.protocol + | ||
|  | 			'//' + | ||
|  | 			loc.hostname + | ||
|  | 			'/api/authn/meta?contact=' + | ||
|  | 			'mailto:' + | ||
|  | 			email | ||
|  | 	}); | ||
|  | 	return body; | ||
|  | }; | ||
|  | 
 | ||
|  | PocketId.auth.verify = async function ({ scheme, email }) { | ||
|  | 	if (!scheme) { | ||
|  | 		scheme = 'mailto:'; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	var loc = window.location; | ||
|  | 	var body = await request({ | ||
|  | 		method: 'GET', | ||
|  | 		url: | ||
|  | 			loc.protocol + | ||
|  | 			'//' + | ||
|  | 			loc.hostname + | ||
|  | 			'/api/authn/verify?contact=' + | ||
|  | 			scheme + | ||
|  | 			email | ||
|  | 	}); | ||
|  | 
 | ||
|  | 	return body; | ||
|  | }; | ||
|  | 
 | ||
|  | PocketId.auth.consume = async function ({ | ||
|  | 	email = '', | ||
|  | 	receipt = '', | ||
|  | 	secret = '', | ||
|  | 	count = 0 | ||
|  | }) { | ||
|  | 	var loc = window.location; | ||
|  | 	var resp = await request({ | ||
|  | 		method: 'GET', | ||
|  | 		url: | ||
|  | 			loc.protocol + | ||
|  | 			'//' + | ||
|  | 			loc.hostname + | ||
|  | 			'/api/authn/consume?contact=' + | ||
|  | 			(email ? 'mailto:' + email : '') + | ||
|  | 			'&receipt=' + | ||
|  | 			receipt + | ||
|  | 			'&secret=' + | ||
|  | 			secret | ||
|  | 	}); | ||
|  | 
 | ||
|  | 	if (resp.body.success) { | ||
|  | 		// There should be a token here
 | ||
|  | 		// (or the pubkey should have been given beforehand)
 | ||
|  | 		return resp.body; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (resp.body.error) { | ||
|  | 		// TODO special errors are hard failures
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if (count > 600) { | ||
|  | 		throw new Error('abandoned login'); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	return timeout(5000).then(function () { | ||
|  | 		console.log('check otp again'); | ||
|  | 		return PocketId.auth.consume({ | ||
|  | 			email, | ||
|  | 			secret, | ||
|  | 			receipt, | ||
|  | 			count: count || 0 | ||
|  | 		}); | ||
|  | 	}); | ||
|  | }; | ||
|  | 
 | ||
|  | async function timeout(ms) { | ||
|  | 	return new Promise(function (resolve) { | ||
|  | 		setTimeout(resolve, ms); | ||
|  | 	}); | ||
|  | } | ||
|  | 
 | ||
|  | var textEncoder = new TextEncoder(); | ||
|  | PocketId.genKey = async function ({ email }) { | ||
|  | 	// Ideally we'd use PBKDF2 or better but... web standards...
 | ||
|  | 	// TODO put a random salt
 | ||
|  | 	var emailU8 = textEncoder.encode(email); | ||
|  | 	var salt = await crypto.subtle.digest('SHA-256', emailU8); | ||
|  | 	var u8 = textEncoder.encode(answer); | ||
|  | 	var hash = await crypto.subtle.digest('SHA-256', u8); | ||
|  | }; |