0
votes

I've been following this article but attempting to do this in TypeScript instead of JavaScript: https://medium.com/@piuccio/running-apollo-server-on-firebase-cloud-functions-265849e9f5b8

However I can't get the behaviour correct on when a Authorization header is not correctly supplied. In the code below I am throwing an Error("No token"). I also attempted to return a rejected Promise but that gave me a HTTP 500 error with {} (an empty JSON response).

import gql from 'graphql-tag';
import { makeExecutableSchema } from 'graphql-tools';
import { Config } from 'apollo-server-cloud-functions';
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';


... resolvers, etc go here...


async function getUser(token: string): Promise<admin.auth.DecodedIdToken> {
    functions.logger.info('token', token);
    if (token === undefined || token === '') {
        throw new Error("No token");
    }
    const idToken = token.split('Bearer ')[1];
    const decodedIdToken = await admin.auth().verifyIdToken(idToken);
    return decodedIdToken;
}

export const config: Config = {
    schema: makeExecutableSchema({
        typeDefs,
        resolvers,
    }),

    // TODO: DISABLE THESE IN PRODUCTION!
    introspection: true,
    playground: true,

    context: ({req, context}): Promise<admin.auth.DecodedIdToken> => {
        // See https://www.apollographql.com/docs/apollo-server/security/authentication/
        functions.logger.info('req headers', req.headers);
        const token = req.headers.authorization || '';
        const user = getUser(token);
        // if (!user) throw new AuthenticationError('you must be logged in');
        return user;
    },
    formatError: formatError,
};

How can I return a valid Promise from getUser() for situations where the API supports anonymous access?

I'd prefer to use the Apollo Server AuthenticationError (as my authentication check) but I can't return null from getUser().

1

1 Answers

0
votes

Following the suggestions over here: How to reject in async/await syntax?

I can modify the type of the Promise to be DecodedIdToken | null and return null as a valid value. If I reject the Promise then an exception is thrown with the error string.

async function getUser(token: string): Promise<DecodedIdToken | null> {
    functions.logger.info('token', token);
    if (token === undefined || token === '') {
        return null;
        // return Promise.reject("No token");
    }
    const idToken = token.split('Bearer ')[1];
    const decodedIdToken = admin.auth().verifyIdToken(idToken);
    return decodedIdToken;
}

export const config: Config = {
    schema: makeExecutableSchema({
        typeDefs,
        resolvers,
    }),

    // TODO: DISABLE THESE IN PRODUCTION!
    introspection: true,
    playground: true,

    context: async ({req, context}): Promise<DecodedIdToken | null> => {
        // See https://www.apollographql.com/docs/apollo-server/security/authentication/
        functions.logger.info('req headers', req.headers);
        const token = req.headers.authorization || '';
        try {
            const user = await getUser(token);
            return Promise.resolve(user);

        } catch (err) {
            return Promise.resolve(null); // "No token"
            // throw new AuthenticationError('you must be logged in');
        }
    },
    formatError: formatError,
};

So depending on whether you want the exception thrown or not, or if you want to use your own exception (such as AuthenticationError for FireStore) you can structure it the way you want.

Here are the different options:

Promise.resolve(value)
Promise.resolve(null)
Promise.reject("Not valid")  -> throws exception Error("Not valid")
throw new AuthenticationError("Not authorized")

... or using the constructor syntax:

return new Promise<DecodedIdToken | null>((resolve, reject) => {
  if (valid) {
    resolve(value);
  } else {
    reject("Not valid");
  }
});