Skip to content

Commit b960fc6

Browse files
committed
add signInWithEmailAndPassword & signInWithEmailLink to auth-next (#3209)
* Cleanup credential inheritance tree * Add signInWithEmailAndPassword and signInWithEmailLink to auth-next * j/k phone credential/provider are actually public * Add tests * PR feedback
1 parent 0f8d410 commit b960fc6

25 files changed

+760
-136
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* @license
3+
* Copyright 2020 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 { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
19+
import * as mockFetch from '../../../test/mock_fetch';
20+
import { expect, use } from 'chai';
21+
import * as chaiAsPromised from 'chai-as-promised';
22+
import { testAuth } from '../../../test/mock_auth';
23+
import { Auth } from '../../model/auth';
24+
import { AnonymousCredential } from './anonymous';
25+
import { mockEndpoint } from '../../../test/api/helper';
26+
import { Endpoint } from '../../api';
27+
import { APIUserInfo } from '../../api/account_management/account';
28+
29+
use(chaiAsPromised);
30+
31+
describe('core/credentials/anonymous', () => {
32+
let auth: Auth;
33+
let credential: AnonymousCredential;
34+
35+
beforeEach(async () => {
36+
auth = await testAuth();
37+
credential = new AnonymousCredential();
38+
});
39+
40+
it('should have an anonymous provider', () => {
41+
expect(credential.providerId).to.eq(ProviderId.ANONYMOUS);
42+
});
43+
44+
it('should have an anonymous sign in method', () => {
45+
expect(credential.signInMethod).to.eq(SignInMethod.ANONYMOUS);
46+
});
47+
48+
describe('#toJSON', () => {
49+
it('throws', () => {
50+
expect(credential.toJSON).to.throw(Error);
51+
});
52+
});
53+
54+
describe('#_getIdTokenResponse', () => {
55+
const serverUser: APIUserInfo = {
56+
localId: 'local-id'
57+
};
58+
59+
beforeEach(() => {
60+
mockFetch.setUp();
61+
mockEndpoint(Endpoint.SIGN_UP, {
62+
idToken: 'id-token',
63+
refreshToken: 'refresh-token',
64+
expiresIn: '1234',
65+
localId: serverUser.localId!
66+
});
67+
});
68+
afterEach(mockFetch.tearDown);
69+
70+
it('calls signUp', async () => {
71+
const idTokenResponse = await credential._getIdTokenResponse(auth);
72+
expect(idTokenResponse.idToken).to.eq('id-token');
73+
expect(idTokenResponse.refreshToken).to.eq('refresh-token');
74+
expect(idTokenResponse.expiresIn).to.eq('1234');
75+
expect(idTokenResponse.localId).to.eq(serverUser.localId);
76+
});
77+
});
78+
79+
describe('#_linkToIdToken', () => {
80+
it('throws', async () => {
81+
await expect(
82+
credential._linkToIdToken(auth, 'id-token')
83+
).to.be.rejectedWith(Error);
84+
});
85+
});
86+
87+
describe('#_matchIdTokenWithUid', () => {
88+
it('throws', () => {
89+
expect(() => credential._matchIdTokenWithUid(auth, 'other-uid')).to.throw(
90+
Error
91+
);
92+
});
93+
});
94+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @license
3+
* Copyright 2020 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 { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
19+
import { signUp } from '../../api/authentication/sign_up';
20+
import { Auth } from '../../model/auth';
21+
import { IdTokenResponse } from '../../model/id_token';
22+
import { debugFail } from '../util/assert';
23+
import { AuthCredential } from '.';
24+
25+
export class AnonymousCredential implements AuthCredential {
26+
providerId = ProviderId.ANONYMOUS;
27+
signInMethod = SignInMethod.ANONYMOUS;
28+
29+
toJSON(): never {
30+
debugFail('Method not implemented.');
31+
}
32+
33+
static fromJSON(_json: object | string): AnonymousCredential | null {
34+
debugFail('Method not implemented');
35+
}
36+
37+
async _getIdTokenResponse(auth: Auth): Promise<IdTokenResponse> {
38+
return signUp(auth, {
39+
returnSecureToken: true
40+
});
41+
}
42+
43+
async _linkToIdToken(_auth: Auth, _idToken: string): Promise<never> {
44+
debugFail("Can't link to an anonymous credential");
45+
}
46+
47+
_matchIdTokenWithUid(_auth: Auth, _uid: string): Promise<never> {
48+
debugFail('Method not implemented.');
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/**
2+
* @license
3+
* Copyright 2020 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 { ProviderId, SignInMethod } from '@firebase/auth-types-exp';
19+
import { expect, use } from 'chai';
20+
import * as chaiAsPromised from 'chai-as-promised';
21+
import { testAuth } from '../../../test/mock_auth';
22+
import { Auth } from '../../model/auth';
23+
import { EmailAuthProvider } from '../providers/email';
24+
import { EmailAuthCredential } from './email';
25+
import * as mockFetch from '../../../test/mock_fetch';
26+
import { mockEndpoint } from '../../../test/api/helper';
27+
import { Endpoint } from '../../api';
28+
import { APIUserInfo } from '../../api/account_management/account';
29+
30+
use(chaiAsPromised);
31+
32+
describe('core/credentials/email', () => {
33+
let auth: Auth;
34+
let apiMock: mockFetch.Route;
35+
const serverUser: APIUserInfo = {
36+
localId: 'local-id'
37+
};
38+
39+
beforeEach(async () => {
40+
auth = await testAuth();
41+
});
42+
43+
context('email & password', () => {
44+
const credential = new EmailAuthCredential(
45+
'some-email',
46+
'some-password',
47+
EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD
48+
);
49+
50+
beforeEach(() => {
51+
mockFetch.setUp();
52+
apiMock = mockEndpoint(Endpoint.SIGN_IN_WITH_PASSWORD, {
53+
idToken: 'id-token',
54+
refreshToken: 'refresh-token',
55+
expiresIn: '1234',
56+
localId: serverUser.localId!
57+
});
58+
});
59+
afterEach(mockFetch.tearDown);
60+
61+
it('should have an email provider', () => {
62+
expect(credential.providerId).to.eq(ProviderId.PASSWORD);
63+
});
64+
65+
it('should have an anonymous sign in method', () => {
66+
expect(credential.signInMethod).to.eq(SignInMethod.EMAIL_PASSWORD);
67+
});
68+
69+
describe('#toJSON', () => {
70+
it('throws', () => {
71+
expect(credential.toJSON).to.throw(Error);
72+
});
73+
});
74+
75+
describe('#_getIdTokenResponse', () => {
76+
it('call sign in with password', async () => {
77+
const idTokenResponse = await credential._getIdTokenResponse(auth);
78+
expect(idTokenResponse.idToken).to.eq('id-token');
79+
expect(idTokenResponse.refreshToken).to.eq('refresh-token');
80+
expect(idTokenResponse.expiresIn).to.eq('1234');
81+
expect(idTokenResponse.localId).to.eq(serverUser.localId);
82+
expect(apiMock.calls[0].request).to.eql({
83+
returnSecureToken: true,
84+
email: 'some-email',
85+
password: 'some-password'
86+
});
87+
});
88+
});
89+
90+
describe('#_linkToIdToken', () => {
91+
it('throws', async () => {
92+
await expect(
93+
credential._linkToIdToken(auth, 'id-token')
94+
).to.be.rejectedWith(Error);
95+
});
96+
});
97+
98+
describe('#_matchIdTokenWithUid', () => {
99+
it('throws', () => {
100+
expect(() =>
101+
credential._matchIdTokenWithUid(auth, 'other-uid')
102+
).to.throw(Error);
103+
});
104+
});
105+
});
106+
107+
context('email link', () => {
108+
const credential = new EmailAuthCredential(
109+
'some-email',
110+
'oob-code',
111+
EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD
112+
);
113+
114+
beforeEach(() => {
115+
mockFetch.setUp();
116+
apiMock = mockEndpoint(Endpoint.SIGN_IN_WITH_EMAIL_LINK, {
117+
idToken: 'id-token',
118+
refreshToken: 'refresh-token',
119+
expiresIn: '1234',
120+
localId: serverUser.localId!
121+
});
122+
});
123+
afterEach(mockFetch.tearDown);
124+
125+
it('should have an email provider', () => {
126+
expect(credential.providerId).to.eq(ProviderId.PASSWORD);
127+
});
128+
129+
it('should have an anonymous sign in method', () => {
130+
expect(credential.signInMethod).to.eq(SignInMethod.EMAIL_LINK);
131+
});
132+
133+
describe('#toJSON', () => {
134+
it('throws', () => {
135+
expect(credential.toJSON).to.throw(Error);
136+
});
137+
});
138+
139+
describe('#_getIdTokenResponse', () => {
140+
it('call sign in with email link', async () => {
141+
const idTokenResponse = await credential._getIdTokenResponse(auth);
142+
expect(idTokenResponse.idToken).to.eq('id-token');
143+
expect(idTokenResponse.refreshToken).to.eq('refresh-token');
144+
expect(idTokenResponse.expiresIn).to.eq('1234');
145+
expect(idTokenResponse.localId).to.eq(serverUser.localId);
146+
expect(apiMock.calls[0].request).to.eql({
147+
email: 'some-email',
148+
oobCode: 'oob-code'
149+
});
150+
});
151+
});
152+
153+
describe('#_linkToIdToken', () => {
154+
it('throws', async () => {
155+
await expect(
156+
credential._linkToIdToken(auth, 'id-token')
157+
).to.be.rejectedWith(Error);
158+
});
159+
});
160+
161+
describe('#_matchIdTokenWithUid', () => {
162+
it('throws', () => {
163+
expect(() =>
164+
credential._matchIdTokenWithUid(auth, 'other-uid')
165+
).to.throw(Error);
166+
});
167+
});
168+
});
169+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* @license
3+
* Copyright 2020 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 * as externs from '@firebase/auth-types-exp';
19+
import { signInWithPassword } from '../../api/authentication/email_and_password';
20+
import { signInWithEmailLink } from '../../api/authentication/email_link';
21+
import { Auth } from '../../model/auth';
22+
import { IdTokenResponse } from '../../model/id_token';
23+
import { AuthErrorCode, AUTH_ERROR_FACTORY } from '../errors';
24+
import { EmailAuthProvider } from '../providers/email';
25+
import { debugFail } from '../util/assert';
26+
import { AuthCredential } from '.';
27+
28+
export class EmailAuthCredential implements AuthCredential {
29+
readonly providerId = EmailAuthProvider.PROVIDER_ID;
30+
31+
constructor(
32+
readonly email: string,
33+
readonly password: string,
34+
readonly signInMethod: externs.SignInMethod
35+
) {}
36+
37+
toJSON(): never {
38+
debugFail('Method not implemented.');
39+
}
40+
41+
static fromJSON(_json: object | string): EmailAuthCredential | null {
42+
debugFail('Method not implemented');
43+
}
44+
45+
async _getIdTokenResponse(auth: Auth): Promise<IdTokenResponse> {
46+
switch (this.signInMethod) {
47+
case EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD:
48+
return signInWithPassword(auth, {
49+
returnSecureToken: true,
50+
email: this.email,
51+
password: this.password
52+
});
53+
case EmailAuthProvider.EMAIL_LINK_SIGN_IN_METHOD:
54+
return signInWithEmailLink(auth, {
55+
email: this.email,
56+
oobCode: this.password
57+
});
58+
default:
59+
throw AUTH_ERROR_FACTORY.create(AuthErrorCode.INTERNAL_ERROR, {
60+
appName: auth.name
61+
});
62+
}
63+
}
64+
65+
async _linkToIdToken(_auth: Auth, _idToken: string): Promise<never> {
66+
debugFail('Method not implemented.');
67+
}
68+
69+
_matchIdTokenWithUid(_auth: Auth, _uid: string): Promise<never> {
70+
debugFail('Method not implemented.');
71+
}
72+
}

0 commit comments

Comments
 (0)