All files / webauthn-perk perk.js

100% Statements 81/81
100% Branches 22/22
100% Functions 3/3
100% Lines 81/81

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 821x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 8x 8x 8x 1x 1x 8x 8x 8x 8x 8x 8x 8x 8x 8x 1x 1x 8x 8x 8x 8x 8x 10x 10x 8x 8x 12x 12x 2x 2x 10x 12x 1x 1x 9x 12x 8x 8x 8x 8x 8x 8x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 8x 8x 8x 12x 12x 12x 12x 3x 3x 3x 12x 1x 1x 8x 8x 8x  
/*eslint-env node */
import url from 'url';
import { promisify } from 'util';
import Ajv from 'ajv';
import sodium_plus from 'sodium-plus';
const { SodiumPlus } = sodium_plus;
import { hash_id, ErrorWithStatus } from './common.js';
import { perk as make_perk_schemas } from './dist/schemas.js';
 
const ajv = new Ajv();
 
function compile(schema) {
    return schema ? ajv.compile(schema) : null;
}
 
export default async function (fastify, options) {
    const {
        valid_ids,
        authz,
        schemas: perk_schemas,
        payload_schema,
        response_schema,
        handler
    } = Object.assign({
        async handler() {
            throw new Error('missing handler');
        }
    }, options.perk_options);
 
    const sodium = await SodiumPlus.auto();
    const valid_hashmap = new Map();
    for (const [id, prefixed_id] of valid_ids) {
        valid_hashmap.set(await hash_id(sodium, prefixed_id), id);
    }
 
    const authorize = promisify((authz_token, cb) => {
        authz.authorize(authz_token, [], (err, payload, hash, rev, credential) => {
            if (err) {
                return cb(err);
            }
            const uri = valid_hashmap.get(hash);
            if (uri === undefined) {
                return cb(new Error(`no matching id for hash: ${hash}`));
            }
            cb(err, { payload, uri, rev, credential });
        });
    });
 
    const schemas = perk_schemas || make_perk_schemas(response_schema);
    const validate_payload = compile(schemas.payload || payload_schema);
 
    fastify.get('/', { schema: schemas.get }, async (request, reply) => {
        const post_response = await fastify.inject({
            method: 'POST',
            url: url.parse(request.raw.url).pathname,
            payload: request.query.assertion,
            headers: {
                'content-type': 'application/json',
                'host': request.headers.host
            }
        });
        reply.code(post_response.statusCode);
        reply.type(post_response.headers['content-type']);
        reply.serializer(x => x);
        reply.send(post_response.payload);
    });
 
    fastify.post('/', { schema: schemas.post }, async (request, reply) => {
        let info;
        try {
            info = await authorize(request.body);
        } catch (ex) {
            ex.statusCode = 400;
            throw ex;
        }
        if (validate_payload && !validate_payload(info.payload)) {
            throw new ErrorWithStatus(ajv.errorsText(validate_payload.errors), 400);
        }
        return await handler(info, request, reply);
    });
}