Skip to content

Commit 86c8077

Browse files
Add iat to fake access token payload (#1022)
* Add iat to fake access token payload * [AUTOMATED]: Prettier Code Styling * Simpler tests * [AUTOMATED]: Prettier Code Styling * Do not clobber iat
1 parent 5118935 commit 86c8077

File tree

2 files changed

+75
-73
lines changed

2 files changed

+75
-73
lines changed

packages/testing/src/api/index.ts

+54-31
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,26 @@ import * as fs from 'fs';
2020
import { FirebaseApp, FirebaseOptions } from '@firebase/app-types';
2121
import { base64 } from '@firebase/util';
2222

23+
/** The default url for the local database emulator. */
2324
const DBURL = 'http://localhost:9000';
24-
25-
class FakeCredentials {
26-
getAccessToken() {
27-
return Promise.resolve({
28-
expires_in: 1000000,
29-
access_token: 'owner'
30-
});
31-
}
32-
getCertificate() {
33-
return null;
34-
}
25+
/** Passing this in tells the emulator to treat you as an admin. */
26+
const ADMIN_TOKEN = 'owner';
27+
/** Create an unsecured JWT for the given auth payload. See https://tools.ietf.org/html/rfc7519#section-6. */
28+
function createUnsecuredJwt(auth: object): string {
29+
// Unsecured JWTs use "none" as the algorithm.
30+
const header = {
31+
alg: 'none',
32+
kid: 'fakekid'
33+
};
34+
// Ensure that the auth payload has a value for 'iat'.
35+
(auth as any).iat = (auth as any).iat || 0;
36+
// Unsecured JWTs use the empty string as a signature.
37+
const signature = '';
38+
return [
39+
base64.encodeString(JSON.stringify(header), /*webSafe=*/ false),
40+
base64.encodeString(JSON.stringify(auth), /*webSafe=*/ false),
41+
signature
42+
].join('.');
3543
}
3644

3745
export function apps(): (FirebaseApp | null)[] {
@@ -41,41 +49,56 @@ export function apps(): (FirebaseApp | null)[] {
4149
export type AppOptions = {
4250
databaseName?: string;
4351
projectId?: string;
44-
auth: object;
52+
auth?: object;
4553
};
54+
/** Construct a FirebaseApp authenticated with options.auth. */
4655
export function initializeTestApp(options: AppOptions): FirebaseApp {
47-
let appOptions: FirebaseOptions;
48-
if ('databaseName' in options) {
56+
return initializeApp(
57+
options.auth ? createUnsecuredJwt(options.auth) : null,
58+
options.databaseName,
59+
options.projectId
60+
);
61+
}
62+
63+
export type AdminAppOptions = {
64+
databaseName?: string;
65+
projectId?: string;
66+
};
67+
/** Construct a FirebaseApp authenticated as an admin user. */
68+
export function initializeAdminApp(options: AdminAppOptions): FirebaseApp {
69+
return initializeApp(ADMIN_TOKEN, options.databaseName, options.projectId);
70+
}
71+
72+
function initializeApp(
73+
accessToken?: string,
74+
databaseName?: string,
75+
projectId?: string
76+
): FirebaseApp {
77+
let appOptions: FirebaseOptions = {};
78+
if (databaseName) {
4979
appOptions = {
50-
databaseURL: DBURL + '?ns=' + options.databaseName
80+
databaseURL: DBURL + '?ns=' + databaseName
5181
};
52-
} else if ('projectId' in options) {
82+
} else if (projectId) {
5383
appOptions = {
54-
projectId: options.projectId
84+
projectId: projectId
5585
};
5686
} else {
5787
throw new Error('neither databaseName or projectId were specified');
5888
}
59-
const header = {
60-
alg: 'RS256',
61-
kid: 'fakekid'
62-
};
63-
const fakeToken = [
64-
base64.encodeString(JSON.stringify(header), /*webSafe=*/ false),
65-
base64.encodeString(JSON.stringify(options.auth), /*webSafe=*/ false),
66-
'fakesignature'
67-
].join('.');
6889
const appName = 'app-' + new Date().getTime() + '-' + Math.random();
69-
const app = firebase.initializeApp(appOptions, appName);
90+
let app = firebase.initializeApp(appOptions, appName);
7091
// hijacking INTERNAL.getToken to bypass FirebaseAuth and allows specifying of auth headers
71-
(app as any).INTERNAL.getToken = () =>
72-
Promise.resolve({ accessToken: fakeToken });
92+
if (accessToken) {
93+
(app as any).INTERNAL.getToken = () =>
94+
Promise.resolve({ accessToken: accessToken });
95+
}
7396
return app;
7497
}
7598

7699
export type LoadDatabaseRulesOptions = {
77-
databaseName: String;
78-
rules: String;
100+
databaseName: string;
101+
rules: string;
79102
rulesPath: fs.PathLike;
80103
};
81104
export function loadDatabaseRules(options: LoadDatabaseRulesOptions): void {

packages/testing/test/database.test.ts

+21-42
Original file line numberDiff line numberDiff line change
@@ -47,56 +47,35 @@ describe('Testing Module Tests', function() {
4747
});
4848
});
4949

50-
it('initializeTestApp() with DatabaseAppOptions uses specified auth.', async function() {
51-
let app = firebase.initializeTestApp({
50+
it('initializeTestApp() with auth=null does not set access token', async function() {
51+
const app = firebase.initializeTestApp({
5252
projectId: 'foo',
53-
auth: {}
53+
auth: null
5454
});
55-
let token = await (app as any).INTERNAL.getToken();
56-
expect(token).to.have.any.keys('accessToken');
57-
let claims = base64.decodeString(
58-
token.accessToken.split('.')[1],
59-
/*webSafe=*/ false
60-
);
61-
expect(claims).to.equal('{}');
62-
63-
app = firebase.initializeTestApp({
64-
projectId: 'foo',
65-
auth: { uid: 'alice' }
66-
});
67-
token = await (app as any).INTERNAL.getToken();
68-
expect(token).to.have.any.keys('accessToken');
69-
claims = base64.decodeString(
70-
token.accessToken.split('.')[1],
71-
/*webSafe=*/ false
72-
);
73-
expect(claims).to.equal('{"uid":"alice"}');
55+
const token = await (app as any).INTERNAL.getToken();
56+
expect(token).to.be.null;
7457
});
7558

76-
it('initializeTestApp() with FirestoreAppOptions uses specified auth.', async function() {
77-
let app = firebase.initializeTestApp({
59+
it('initializeTestApp() with auth sets the correct access token', async function() {
60+
const auth = { uid: 'alice' };
61+
const app = firebase.initializeTestApp({
7862
projectId: 'foo',
79-
auth: {}
63+
auth: auth
8064
});
81-
let token = await (app as any).INTERNAL.getToken();
82-
expect(token).to.have.any.keys('accessToken');
83-
let claims = base64.decodeString(
84-
token.accessToken.split('.')[1],
85-
/*webSafe=*/ false
65+
const token = await (app as any).INTERNAL.getToken();
66+
expect(token).to.have.keys('accessToken');
67+
const claims = JSON.parse(
68+
base64.decodeString(token.accessToken.split('.')[1], /*webSafe=*/ false)
8669
);
87-
expect(claims).to.equal('{}');
70+
// We add an 'iat' field.
71+
expect(claims).to.deep.equal({ uid: auth.uid, iat: 0 });
72+
});
8873

89-
app = firebase.initializeTestApp({
90-
projectId: 'foo',
91-
auth: { uid: 'alice' }
92-
});
93-
token = await (app as any).INTERNAL.getToken();
94-
expect(token).to.have.any.keys('accessToken');
95-
claims = base64.decodeString(
96-
token.accessToken.split('.')[1],
97-
/*webSafe=*/ false
98-
);
99-
expect(claims).to.equal('{"uid":"alice"}');
74+
it('initializeAdminApp() sets the access token to "owner"', async function() {
75+
const app = firebase.initializeAdminApp({ projectId: 'foo' });
76+
const token = await (app as any).INTERNAL.getToken();
77+
expect(token).to.have.keys('accessToken');
78+
expect(token.accessToken).to.be.string('owner');
10079
});
10180

10281
it('loadDatabaseRules() throws if no databaseName or rulesPath', async function() {

0 commit comments

Comments
 (0)