Skip to content

Commit aba574a

Browse files
committed
First.
1 parent 00b0059 commit aba574a

File tree

10 files changed

+137
-11
lines changed

10 files changed

+137
-11
lines changed

common/api-review/auth.api.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,9 @@ export function connectAuthEmulator(auth: Auth, url: string, options?: {
296296
disableWarnings: boolean;
297297
}): void;
298298

299+
// @public
300+
export const cookiePersistence: Persistence;
301+
299302
// @public
300303
export function createUserWithEmailAndPassword(auth: Auth, email: string, password: string): Promise<UserCredential>;
301304

@@ -596,7 +599,7 @@ export interface PasswordValidationStatus {
596599

597600
// @public
598601
export interface Persistence {
599-
readonly type: 'SESSION' | 'LOCAL' | 'NONE';
602+
readonly type: 'SESSION' | 'LOCAL' | 'NONE' | 'COOKIE';
600603
}
601604

602605
// @public

packages/auth/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export * from './src';
4343

4444
// persistence
4545
import { browserLocalPersistence } from './src/platform_browser/persistence/local_storage';
46+
import { cookiePersistence } from './src/platform_browser/persistence/cookie_storage';
4647
import { browserSessionPersistence } from './src/platform_browser/persistence/session_storage';
4748
import { indexedDBLocalPersistence } from './src/platform_browser/persistence/indexed_db';
4849

@@ -83,6 +84,7 @@ import { getAuth } from './src/platform_browser';
8384

8485
export {
8586
browserLocalPersistence,
87+
cookiePersistence,
8688
browserSessionPersistence,
8789
indexedDBLocalPersistence,
8890
PhoneAuthProvider,

packages/auth/src/api/index.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { AuthInternal, ConfigInternal } from '../model/auth';
3131
import { IdTokenResponse, TaggedWithTokenResponse } from '../model/id_token';
3232
import { IdTokenMfaResponse } from './authentication/mfa';
3333
import { SERVER_ERROR_MAP, ServerError, ServerErrorMap } from './errors';
34+
import { PersistenceType } from '../core/persistence';
3435

3536
export const enum HttpMethod {
3637
POST = 'POST',
@@ -268,11 +269,21 @@ export function _getFinalTarget(
268269
): string {
269270
const base = `${host}${path}?${query}`;
270271

271-
if (!(auth as AuthInternal).config.emulator) {
272-
return `${auth.config.apiScheme}://${base}`;
272+
const finalTarget = (auth as AuthInternal).config.emulator
273+
? _emulatorUrl(auth.config as ConfigInternal, base)
274+
: `${auth.config.apiScheme}://${base}`;
275+
276+
// TODO get the exchange URL from the persistence method
277+
// don't use startsWith v1/accounts...
278+
if (
279+
(auth as AuthInternal)._getPersistence() === PersistenceType.COOKIE &&
280+
(path.startsWith('/v1/accounts:signIn') || path === Endpoint.TOKEN)
281+
) {
282+
const params = new URLSearchParams({ finalTarget });
283+
return `${window.location.origin}/__cookies__?${params.toString()}`;
273284
}
274285

275-
return _emulatorUrl(auth.config as ConfigInternal, base);
286+
return finalTarget;
276287
}
277288

278289
export function _parseEnforcementState(

packages/auth/src/core/auth/auth_impl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
845845
}
846846

847847
async _getAppCheckToken(): Promise<string | undefined> {
848+
// @ts-ignore
848849
if (_isFirebaseServerApp(this.app) && this.app.settings.appCheckToken) {
849850
return this.app.settings.appCheckToken;
850851
}

packages/auth/src/core/persistence/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import { Persistence } from '../../model/public_types';
1919
export const enum PersistenceType {
2020
SESSION = 'SESSION',
2121
LOCAL = 'LOCAL',
22-
NONE = 'NONE'
22+
NONE = 'NONE',
23+
COOKIE = 'COOKIE'
2324
}
2425

2526
export type PersistedBlob = Record<string, unknown>;

packages/auth/src/core/persistence/persistence_user_manager.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
import { getAccountInfo } from '../../api/account_management/account';
1819
import { ApiKey, AppName, AuthInternal } from '../../model/auth';
1920
import { UserInternal } from '../../model/user';
2021
import { PersistedBlob, PersistenceInternal } from '../persistence';
@@ -66,7 +67,20 @@ export class PersistenceUserManager {
6667
}
6768

6869
async getCurrentUser(): Promise<UserInternal | null> {
69-
const blob = await this.persistence._get<PersistedBlob>(this.fullUserKey);
70+
const blob = await this.persistence._get<PersistedBlob | string>(
71+
this.fullUserKey
72+
);
73+
if (typeof blob === 'string') {
74+
if (blob === '0') {
75+
return null;
76+
}
77+
const response = await getAccountInfo(this.auth, { idToken: blob });
78+
return await UserImpl._fromGetAccountInfoResponse(
79+
this.auth,
80+
response,
81+
blob
82+
);
83+
}
7084
return blob ? UserImpl._fromJSON(this.auth, blob) : null;
7185
}
7286

@@ -140,9 +154,19 @@ export class PersistenceUserManager {
140154
// persistence, we will (but only if that persistence supports migration).
141155
for (const persistence of persistenceHierarchy) {
142156
try {
143-
const blob = await persistence._get<PersistedBlob>(key);
144-
if (blob) {
145-
const user = UserImpl._fromJSON(auth, blob); // throws for unparsable blob (wrong format)
157+
const blob = await persistence._get<PersistedBlob | string>(key);
158+
if (blob && blob !== '0') {
159+
let user: UserInternal;
160+
if (typeof blob === 'string') {
161+
const response = await getAccountInfo(auth, { idToken: blob });
162+
user = await UserImpl._fromGetAccountInfoResponse(
163+
auth,
164+
response,
165+
blob
166+
);
167+
} else {
168+
user = UserImpl._fromJSON(auth, blob); // throws for unparsable blob (wrong format)
169+
}
146170
if (persistence !== selectedPersistence) {
147171
userToMigrate = user;
148172
}

packages/auth/src/model/public_types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,9 @@ export interface Persistence {
341341
* - 'SESSION' is used for temporary persistence such as `sessionStorage`.
342342
* - 'LOCAL' is used for long term persistence such as `localStorage` or `IndexedDB`.
343343
* - 'NONE' is used for in-memory, or no persistence.
344+
* - 'COOKIE' is used for cookies, useful for server-side rendering.
344345
*/
345-
readonly type: 'SESSION' | 'LOCAL' | 'NONE';
346+
readonly type: 'SESSION' | 'LOCAL' | 'NONE' | 'COOKIE';
346347
}
347348

348349
/**
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { Persistence } from '../../model/public_types';
19+
20+
import {
21+
PersistenceInternal,
22+
PersistenceType,
23+
PersistenceValue,
24+
StorageEventListener
25+
} from '../../core/persistence';
26+
27+
export class CookiePersistence implements PersistenceInternal {
28+
static type: 'COOKIE' = 'COOKIE';
29+
readonly type = PersistenceType.COOKIE;
30+
listeners: Map<StorageEventListener, (e: any) => void> = new Map();
31+
32+
async _isAvailable(): Promise<boolean> {
33+
return navigator.hasOwnProperty('cookieEnabled') ?
34+
navigator.cookieEnabled :
35+
true;
36+
}
37+
38+
async _set(_key: string, _value: PersistenceValue): Promise<void> {
39+
return;
40+
}
41+
42+
async _get<T extends PersistenceValue>(key: string): Promise<T | null> {
43+
const cookie = await (window as any).cookieStore.get(key);
44+
return cookie?.value;
45+
}
46+
47+
async _remove(key: string): Promise<void> {
48+
const cookie = await (window as any).cookieStore.get(key);
49+
if (!cookie) {
50+
return;
51+
}
52+
await (window as any).cookieStore.set({ ...cookie, value: "0" });
53+
await fetch(`/__cookies__`, { method: 'DELETE' }).catch(() => undefined);
54+
}
55+
56+
_addListener(_key: string, _listener: StorageEventListener): void {
57+
// TODO fallback to polling if cookieStore is not available
58+
const cb = (event: any) => {
59+
const cookie = event.changed.find((change: any) => change.name === _key);
60+
if (cookie) {
61+
_listener(cookie.value);
62+
}
63+
};
64+
this.listeners.set(_listener, cb);
65+
(window as any).cookieStore.addEventListener('change', cb);
66+
}
67+
68+
_removeListener(_key: string, _listener: StorageEventListener): void {
69+
const cb = this.listeners.get(_listener);
70+
if (!cb) {
71+
return;
72+
}
73+
(window as any).cookieStore.removeEventListener('change', cb);
74+
}
75+
}
76+
77+
/**
78+
* An implementation of {@link Persistence} of type 'NONE'.
79+
*
80+
* @public
81+
*/
82+
export const cookiePersistence: Persistence = CookiePersistence;

packages/auth/src/platform_node/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class FailClass {
8181

8282
export const browserLocalPersistence = inMemoryPersistence;
8383
export const browserSessionPersistence = inMemoryPersistence;
84+
export const cookiePersistence = inMemoryPersistence;
8485
export const indexedDBLocalPersistence = inMemoryPersistence;
8586
export const browserPopupRedirectResolver = NOT_AVAILABLE_ERROR;
8687
export const PhoneAuthProvider = FailClass;
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"extends": "../../config/api-extractor.json",
33
// Point it to your entry point d.ts file.
4-
"mainEntryPointFilePath": "<projectFolder>/dist/rules-unit-testing/index.d.ts"
4+
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts"
55
}

0 commit comments

Comments
 (0)