Skip to content
This repository was archived by the owner on Apr 6, 2022. It is now read-only.
Merged
7 changes: 7 additions & 0 deletions packages/auth/lib/Sessions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { NoSessionError, QueryEmailMismatchError, DestroySessionError } = require('./errors')
const { decode, match } = require('@orbiting/backend-modules-base64u')

const destroySession = async (req) => {
return new Promise((resolve, reject) => {
Expand All @@ -16,6 +17,12 @@ const sessionByToken = async ({ pgdb, token, email: emailFromQuery, ...meta }) =
const session = await Sessions.findOne({
'sess @>': { token }
})

// Recognize and decode urlsafe base64 encoded email address.
if (match(emailFromQuery)) {
emailFromQuery = decode(emailFromQuery)
}

if (!session) {
throw new NoSessionError({ token, emailFromQuery, ...meta })
}
Expand Down
3 changes: 2 additions & 1 deletion packages/auth/lib/signIn.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const {
sendMail,
sendMailTemplate
} = require('@orbiting/backend-modules-mail')
const { encode } = require('@orbiting/backend-modules-base64u')

checkEnv([
'FRONTEND_BASE_URL',
Expand Down Expand Up @@ -75,7 +76,7 @@ module.exports = async (_email, context, pgdb, req) => {
`${FRONTEND_BASE_URL}/mitteilung?` +
querystring.stringify({
type: 'token-authorization',
email,
email: encode(email),
context,
token
})
Expand Down
38 changes: 38 additions & 0 deletions packages/base64u/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const urlDecode = base64u =>
`${base64u}${'==='.slice((base64u.length + 3) % 4)}`
.replace(/-/g, '+')
.replace(/_/g, '/')

const urlEncode = string => string
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')

// see https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding, section about "Unicode Problem"
const toUnicode = string => encodeURIComponent(string).replace(
/%([0-9A-F]{2})/g,
function toSolidBytes (match, p1) {
return String.fromCharCode('0x' + p1)
}
)

// see https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding, section about "Unicode Problem"
const fromUnicode = string => decodeURIComponent(
string.split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
}).join('')
)

module.exports = {
decode: base64u => {
return typeof window === 'object'
? fromUnicode(window.atob(urlDecode(base64u)))
: Buffer.from(urlDecode(base64u), 'base64').toString('utf8')
},
encode: string => urlEncode(
typeof window === 'object'
? window.btoa(toUnicode(string))
: Buffer.from(string, 'utf8').toString('base64')
),
match: base64u => /^[A-Za-z0-9\-_]+$/.test(base64u)
}
127 changes: 127 additions & 0 deletions packages/base64u/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
const { encode, decode, match } = require('./index')
const abab = require('abab')
const test = require('tape')

const testSeries = (env, runEnv) => {
[
{ string: 'heidi', base64u: 'aGVpZGk' },
{ string: 'peter?_', base64u: 'cGV0ZXI_Xw' }, // Not urlsafe: cGV0ZXI/Xw
{ string: '12345><', base64u: 'MTIzNDU-PA' }, // Not urlsafe: MTIzNDU+PA
{ string: 'äöüáàâéèê', base64u: 'w6TDtsO8w6HDoMOiw6nDqMOq' },
{ string: 'π', base64u: 'z4A' },
{ string: '😈', base64u: '8J-YiA' }, // Not urlsafe: 8J+YiA
{ string: '\n', base64u: 'Cg' }
]
.forEach(({ title, string, base64u }) => {
test(
`(${env}) base64u ${string} <-> ${base64u}`,
assert => runEnv(
assert,
assert => {
const encoded = encode(string)
assert.equal(encoded, base64u)

const decoded = decode(encoded)
assert.equal(decoded, string)

assert.end()
}
)
)
})

test(
`(${env}) base64u.decode w/ block padding "="`,
assert => runEnv(
assert,
assert => {
assert.equal(decode('a2xhcmE='), 'klara')
assert.end()
}
)
)

test(
`(${env}) base64u.decode w/o block padding "="`,
assert => runEnv(
assert,
assert => {
assert.equal(decode('a2xhcmE'), 'klara')
assert.end()
}
)
)

test(
`(${env}) base64u.match "a2xhcmE=" -> false`,
assert => runEnv(
assert,
assert => {
assert.equal(match('a2xhcmE='), false)
assert.end()
}
)
)

test(
`(${env}) base64u.match "a2xhcmE" -> true`,
assert => runEnv(
assert,
assert => {
assert.equal(match('a2xhcmE'), true)
assert.end()
}
)
)

test(
`(${env}) base64u.match "cGV0ZXI_Xw" -> true`,
assert => runEnv(
assert,
assert => {
assert.equal(match('cGV0ZXI_Xw'), true)
assert.end()
}
)
)

test(
`(${env}) base64u.match "cGV0ZXI/Xw" -> false`,
assert => runEnv(
assert,
assert => {
assert.equal(match('cGV0ZXI/Xw'), false)
assert.end()
}
)
)

test(
`(${env}) base64u.match "cGV0ZXI/Xw" -> false`,
assert => runEnv(
assert,
assert => {
assert.equal(match('cGV0ZXI/Xw'), false)
assert.end()
}
)
)
}

// Test Node.JS path
testSeries(
'node',
(assert, asserationFn) => {
asserationFn(assert)
}
)

// Test (emulated) Browser window global variable
testSeries(
'browser',
(assert, asserationFn) => {
global.window = abab
asserationFn(assert)
global.window = undefined
}
)
23 changes: 23 additions & 0 deletions packages/base64u/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@orbiting/backend-modules-base64u",
"version": "1.0.0",
"description": "Encode and decode strings to base64 in an urlsafe manner",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/orbiting/backends.git"
},
"author": "Patrick Venetz <patrick.venetz@republik.ch>",
"license": "AGPL-3.0",
"bugs": {
"url": "https://github.com/orbiting/backends/issues"
},
"homepage": "https://github.com/orbiting/backends#readme",
"devDependencies": {
"abab": "^2.0.0",
"tape": "^4.9.0"
},
"scripts": {
"test": "NODE_ENV=development tape \"**/*.test.js\""
}
}
30 changes: 28 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@
"@types/events" "*"
"@types/node" "*"

abab@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"

abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
Expand Down Expand Up @@ -2633,7 +2637,7 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
mkdirp ">=0.5 0"
rimraf "2"

function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1, function-bind@~1.1.0:
function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1, function-bind@~1.1.0, function-bind@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"

Expand Down Expand Up @@ -4628,6 +4632,10 @@ object-inspect@~1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.3.0.tgz#5b1eb8e6742e2ee83342a637034d844928ba2f6d"

object-inspect@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.5.0.tgz#9d876c11e40f485c79215670281b767488f9bfe3"

object-keys@^1.0.11, object-keys@^1.0.8:
version "1.0.11"
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
Expand Down Expand Up @@ -5716,7 +5724,7 @@ resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"

resolve@^1.1.6, resolve@^1.1.7:
resolve@^1.1.6, resolve@^1.1.7, resolve@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
dependencies:
Expand Down Expand Up @@ -6460,6 +6468,24 @@ tape@^4.5.0:
string.prototype.trim "~1.1.2"
through "~2.3.8"

tape@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/tape/-/tape-4.9.0.tgz#855c08360395133709d34d3fbf9ef341eb73ca6a"
dependencies:
deep-equal "~1.0.1"
defined "~1.0.0"
for-each "~0.3.2"
function-bind "~1.1.1"
glob "~7.1.2"
has "~1.0.1"
inherits "~2.0.3"
minimist "~1.2.0"
object-inspect "~1.5.0"
resolve "~1.5.0"
resumer "~0.0.0"
string.prototype.trim "~1.1.2"
through "~2.3.8"

tar-fs@^1.13.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.16.0.tgz#e877a25acbcc51d8c790da1c57c9cf439817b896"
Expand Down