@authduo/authduo
Version:
Free User-sovereign Authentication for the World
101 lines (82 loc) • 2.52 kB
text/typescript
import {Base64, Text} from "@benev/slate"
import {Passport} from "./passport.js"
import {ensure} from "./utils/ensure.js"
import {crushUsername} from "./utils/crush-username.js"
import {PassportsFileData, PassportData} from "./types.js"
export class PassportsFile {
static readonly format = "authduo.org passports"
static readonly extension = "passport"
static readonly version = 3
static #ingestData(raw: any): PassportsFileData {
let data: PassportsFileData | null = null
if (
!("format" in raw) ||
!("version" in raw) ||
raw.format !== PassportsFile.format)
throw new Error(`invalid format`)
switch (raw.version) {
case 0:
case 1:
case 2: throw new Error(`invalid version ${raw.version}`)
case 3: data = raw
}
if (!data)
throw new Error(`unknown version ${raw.version}`)
return {
format: ensure.string("format", data.format),
version: ensure.number("version", data.version),
passports: ensure.array("array", data.passports.map((p): PassportData => ({
name: ensure.string("name", p.name),
created: ensure.number("created", p.created),
keypair: {
thumbprint: ensure.string("thumbprint", p.keypair.thumbprint),
publicKey: ensure.string("public", p.keypair.publicKey),
privateKey: ensure.string("private", p.keypair.privateKey),
},
})))
}
}
static fromData(raw: any) {
const data = PassportsFile.#ingestData(raw)
const passports = new this()
passports.add(...data.passports.map(d => Passport.fromData(d)))
return passports
}
#map = new Map<string, Passport>()
list() {
return [...this.#map.values()]
}
add(...additions: Passport[]) {
for (const passport of additions)
this.#map.set(passport.thumbprint, passport)
return this
}
delete(...deletions: Passport[]) {
for (const passport of deletions)
this.#map.delete(passport.thumbprint)
return this
}
deleteAll() {
this.#map.clear()
return this
}
toData(): PassportsFileData {
return {
format: PassportsFile.format,
version: PassportsFile.version,
passports: [...this.#map.values()]
.map(passport => passport.toData()),
}
}
filename() {
const passports = this.list()
return passports.length === 1
? `${crushUsername(passports.at(0)!.name)}.${PassportsFile.extension}`
: `passports.${PassportsFile.extension}`
}
href() {
const text = JSON.stringify(this.toData(), undefined, "\t")
const encoded = Base64.string(Text.bytes(text))
return `data:application/octet-stream;base64,${encoded}`
}
}