Skip to content

Commit 0915849

Browse files
authored
Merge d72ccbc into f73b24d
2 parents f73b24d + d72ccbc commit 0915849

File tree

10 files changed

+209
-15
lines changed

10 files changed

+209
-15
lines changed

common/api-review/auth.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,9 @@ export class RecaptchaVerifier implements ApplicationVerifierInternal {
725725
// @public
726726
export function reload(user: User): Promise<void>;
727727

728+
// @public
729+
export function revokeAccessToken(auth: Auth, token: string): Promise<void>;
730+
728731
// Warning: (ae-forgotten-export) The symbol "FederatedAuthProvider" needs to be exported by the entry point index.d.ts
729732
//
730733
// @public

docs-devsite/auth.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Firebase Authentication
3535
| [isSignInWithEmailLink(auth, emailLink)](./auth.md#issigninwithemaillink) | Checks if an incoming link is a sign-in with email link suitable for [signInWithEmailLink()](./auth.md#signinwithemaillink)<!-- -->. |
3636
| [onAuthStateChanged(auth, nextOrObserver, error, completed)](./auth.md#onauthstatechanged) | Adds an observer for changes to the user's sign-in state. |
3737
| [onIdTokenChanged(auth, nextOrObserver, error, completed)](./auth.md#onidtokenchanged) | Adds an observer for changes to the signed-in user's ID token. |
38+
| [revokeAccessToken(auth, token)](./auth.md#revokeaccesstoken) | Revokes the given access token. Currently only supports Apple OAuth Access token. |
3839
| [sendPasswordResetEmail(auth, email, actionCodeSettings)](./auth.md#sendpasswordresetemail) | Sends a password reset email to the given email address. |
3940
| [sendSignInLinkToEmail(auth, email, actionCodeSettings)](./auth.md#sendsigninlinktoemail) | Sends a sign-in email link to the user with the specified email. |
4041
| [setPersistence(auth, persistence)](./auth.md#setpersistence) | Changes the type of persistence on the [Auth](./auth.auth.md#auth_interface) instance for the currently saved <code>Auth</code> session and applies this type of persistence for future sign-in requests, including sign-in with redirect requests. |
@@ -598,6 +599,27 @@ export declare function onIdTokenChanged(auth: Auth, nextOrObserver: NextOrObser
598599

599600
[Unsubscribe](./util.md#unsubscribe)
600601

602+
## revokeAccessToken()
603+
604+
Revokes the given access token. Currently only supports Apple OAuth Access token.
605+
606+
<b>Signature:</b>
607+
608+
```typescript
609+
export declare function revokeAccessToken(auth: Auth, token: string): Promise<void>;
610+
```
611+
612+
### Parameters
613+
614+
| Parameter | Type | Description |
615+
| --- | --- | --- |
616+
| auth | [Auth](./auth.auth.md#auth_interface) | |
617+
| token | string | |
618+
619+
<b>Returns:</b>
620+
621+
Promise&lt;void&gt;
622+
601623
## sendPasswordResetEmail()
602624

603625
Sends a password reset email to the given email address.

packages/auth/demo/src/index.js

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ import {
7373
browserPopupRedirectResolver,
7474
connectAuthEmulator,
7575
initializeRecaptchaConfig,
76-
validatePassword
76+
validatePassword,
77+
revokeAccessToken
7778
} from '@firebase/auth';
7879

7980
import { config } from './config';
@@ -1730,13 +1731,58 @@ function logAdditionalUserInfo(response) {
17301731
* Deletes the user account.
17311732
*/
17321733
function onDelete() {
1733-
activeUser()
1734-
['delete']()
1735-
.then(() => {
1736-
log('User successfully deleted.');
1737-
alertSuccess('User successfully deleted.');
1738-
refreshUserData();
1739-
}, onAuthError);
1734+
var isAppleProviderLinked = false;
1735+
1736+
for (const provider of activeUser().providerData) {
1737+
if (provider.providerId == 'apple.com') {
1738+
isAppleProviderLinked = true;
1739+
break;
1740+
}
1741+
}
1742+
1743+
if (isAppleProviderLinked) {
1744+
revokeAppleTokenAndDeleteUser();
1745+
} else {
1746+
activeUser()
1747+
['delete']()
1748+
.then(() => {
1749+
log('User successfully deleted.');
1750+
alertSuccess('User successfully deleted.');
1751+
refreshUserData();
1752+
}, onAuthError);
1753+
}
1754+
}
1755+
1756+
function revokeAppleTokenAndDeleteUser() {
1757+
// Re-auth then revoke the token
1758+
const provider = new OAuthProvider('apple.com');
1759+
provider.addScope('email');
1760+
provider.addScope('name');
1761+
1762+
const auth = getAuth();
1763+
signInWithPopup(auth, provider).then(result => {
1764+
// The signed-in user info.
1765+
const user = result.user;
1766+
const credential = OAuthProvider.credentialFromResult(result);
1767+
const accessToken = credential.accessToken;
1768+
1769+
revokeAccessToken(auth, accessToken)
1770+
.then(() => {
1771+
log('Token successfully revoked.');
1772+
1773+
// Usual user deletion
1774+
activeUser()
1775+
['delete']()
1776+
.then(() => {
1777+
log('User successfully deleted.');
1778+
alertSuccess('User successfully deleted.');
1779+
refreshUserData();
1780+
}, onAuthError);
1781+
})
1782+
.catch(error => {
1783+
console.log(error.message);
1784+
});
1785+
});
17401786
}
17411787

17421788
/**

packages/auth/src/api/authentication/token.test.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@ import chaiAsPromised from 'chai-as-promised';
2121

2222
import { FirebaseError, getUA, querystringDecode } from '@firebase/util';
2323

24-
import { HttpHeader } from '../';
24+
import { Endpoint, HttpHeader } from '../';
25+
import { mockEndpoint } from '../../../test/helpers/api/helper';
2526
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
2627
import * as fetch from '../../../test/helpers/mock_fetch';
2728
import { ServerError } from '../errors';
28-
import { Endpoint, requestStsToken } from './token';
29+
import { TokenType, requestStsToken, revokeToken } from './token';
2930
import { SDK_VERSION } from '@firebase/app';
3031
import { _getBrowserName } from '../../core/util/browser';
3132

@@ -143,3 +144,63 @@ describe('requestStsToken', () => {
143144
});
144145
});
145146
});
147+
148+
describe('api/authentication/revokeToken', () => {
149+
const request = {
150+
providerId: 'provider-id',
151+
tokenType: TokenType.ACCESS_TOKEN,
152+
token: 'token',
153+
idToken: 'id-token'
154+
};
155+
156+
let auth: TestAuth;
157+
158+
beforeEach(async () => {
159+
auth = await testAuth();
160+
fetch.setUp();
161+
});
162+
163+
afterEach(() => {
164+
fetch.tearDown();
165+
});
166+
167+
it('should POST to the correct endpoint', async () => {
168+
const mock = mockEndpoint(Endpoint.REVOKE_TOKEN, {});
169+
170+
auth.tenantId = 'tenant-id';
171+
await revokeToken(auth, request);
172+
// Currently, backend returns an empty response.
173+
expect(mock.calls[0].request).to.eql({ ...request, tenantId: 'tenant-id' });
174+
expect(mock.calls[0].method).to.eq('POST');
175+
expect(mock.calls[0].headers!.get(HttpHeader.CONTENT_TYPE)).to.eq(
176+
'application/json'
177+
);
178+
expect(mock.calls[0].headers!.get(HttpHeader.X_CLIENT_VERSION)).to.eq(
179+
'testSDK/0.0.0'
180+
);
181+
});
182+
183+
it('should handle errors', async () => {
184+
const mock = mockEndpoint(
185+
Endpoint.REVOKE_TOKEN,
186+
{
187+
error: {
188+
code: 400,
189+
message: ServerError.INVALID_IDP_RESPONSE,
190+
errors: [
191+
{
192+
message: ServerError.INVALID_IDP_RESPONSE
193+
}
194+
]
195+
}
196+
},
197+
400
198+
);
199+
200+
await expect(revokeToken(auth, request)).to.be.rejectedWith(
201+
FirebaseError,
202+
'Firebase: The supplied auth credential is malformed or has expired. (auth/invalid-credential).'
203+
);
204+
expect(mock.calls[0].request).to.eql(request);
205+
});
206+
});

packages/auth/src/api/authentication/token.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@ import { querystring } from '@firebase/util';
2222
import {
2323
_getFinalTarget,
2424
_performFetchWithErrorHandling,
25+
_performApiRequest,
26+
_addTidIfNecessary,
2527
HttpMethod,
26-
HttpHeader
28+
HttpHeader,
29+
Endpoint
2730
} from '../index';
2831
import { FetchProvider } from '../../core/util/fetch_provider';
2932
import { Auth } from '../../model/public_types';
3033
import { AuthInternal } from '../../model/auth';
3134

32-
export const enum Endpoint {
33-
TOKEN = '/v1/token'
35+
export const enum TokenType {
36+
REFRESH_TOKEN = 'REFRESH_TOKEN',
37+
ACCESS_TOKEN = 'ACCESS_TOKEN'
3438
}
3539

3640
/** The server responses with snake_case; we convert to camelCase */
@@ -46,6 +50,16 @@ export interface RequestStsTokenResponse {
4650
refreshToken: string;
4751
}
4852

53+
export interface RevokeTokenRequest {
54+
providerId: string;
55+
tokenType: TokenType;
56+
token: string;
57+
idToken: string;
58+
tenantId?: string;
59+
}
60+
61+
export interface RevokeTokenResponse {}
62+
4963
export async function requestStsToken(
5064
auth: Auth,
5165
refreshToken: string
@@ -85,3 +99,15 @@ export async function requestStsToken(
8599
refreshToken: response.refresh_token
86100
};
87101
}
102+
103+
export async function revokeToken(
104+
auth: Auth,
105+
request: RevokeTokenRequest
106+
): Promise<RevokeTokenResponse> {
107+
return _performApiRequest<RevokeTokenRequest, RevokeTokenResponse>(
108+
auth,
109+
HttpMethod.POST,
110+
Endpoint.REVOKE_TOKEN,
111+
_addTidIfNecessary(auth, request)
112+
);
113+
}

packages/auth/src/api/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ export const enum Endpoint {
6868
WITHDRAW_MFA = '/v2/accounts/mfaEnrollment:withdraw',
6969
GET_PROJECT_CONFIG = '/v1/projects',
7070
GET_RECAPTCHA_CONFIG = '/v2/recaptchaConfig',
71-
GET_PASSWORD_POLICY = '/v2/passwordPolicy'
71+
GET_PASSWORD_POLICY = '/v2/passwordPolicy',
72+
TOKEN = '/v1/token',
73+
REVOKE_TOKEN = '/v2/accounts:revokeToken'
7274
}
7375

7476
export const enum RecaptchaClientType {

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ import { _getInstance } from '../util/instantiator';
6363
import { _getUserLanguage } from '../util/navigator';
6464
import { _getClientVersion } from '../util/version';
6565
import { HttpHeader } from '../../api';
66+
import {
67+
RevokeTokenRequest,
68+
TokenType,
69+
revokeToken
70+
} from '../../api/authentication/token';
6671
import { AuthMiddlewareQueue } from './middleware';
6772
import { RecaptchaConfig } from '../../platform_browser/recaptcha/recaptcha';
6873
import { _logWarn } from '../util/log';
@@ -514,6 +519,26 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
514519
});
515520
}
516521

522+
/**
523+
* Revokes the given access token. Currently only supports Apple OAuth Access token.
524+
*/
525+
async revokeAccessToken(token: string): Promise<void> {
526+
if (this.currentUser) {
527+
const idToken = await this.currentUser.getIdToken();
528+
// Generalize this to accept other providers once supported.
529+
const request: RevokeTokenRequest = {
530+
providerId: 'apple.com',
531+
tokenType: TokenType.ACCESS_TOKEN,
532+
token,
533+
idToken
534+
};
535+
if (this.tenantId != null) {
536+
request.tenantId = this.tenantId;
537+
}
538+
await revokeToken(this, request);
539+
}
540+
}
541+
517542
toJSON(): object {
518543
return {
519544
apiKey: this.config.apiKey,

packages/auth/src/core/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,14 @@ export function signOut(auth: Auth): Promise<void> {
245245
return getModularInstance(auth).signOut();
246246
}
247247

248+
/**
249+
* Revokes the given access token. Currently only supports Apple OAuth Access token.
250+
*/
251+
export function revokeAccessToken(auth: Auth, token: string): Promise<void> {
252+
const authInternal = _castAuth(auth);
253+
return authInternal.revokeAccessToken(token);
254+
}
255+
248256
export { initializeAuth } from './auth/initialize';
249257
export { connectAuthEmulator } from './auth/emulator';
250258

packages/auth/src/core/user/token_manager.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ import { FirebaseError } from '@firebase/util';
2323

2424
import { testAuth, TestAuth } from '../../../test/helpers/mock_auth';
2525
import * as fetch from '../../../test/helpers/mock_fetch';
26-
import { Endpoint } from '../../api/authentication/token';
2726
import { IdTokenResponse } from '../../model/id_token';
2827
import { StsTokenManager, Buffer } from './token_manager';
2928
import { FinalizeMfaResponse } from '../../api/authentication/mfa';
3029
import { makeJWT } from '../../../test/helpers/jwt';
30+
import { Endpoint } from '../../api';
3131

3232
use(chaiAsPromised);
3333

packages/auth/src/model/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,5 @@ export interface AuthInternal extends Auth {
105105
useDeviceLanguage(): void;
106106
signOut(): Promise<void>;
107107
validatePassword(password: string): Promise<PasswordValidationStatus>;
108+
revokeAccessToken(token: string): Promise<void>;
108109
}

0 commit comments

Comments
 (0)