Skip to content

Commit bc38580

Browse files
authored
multi-tenant support in firebaseui (#621)
Added support for multi-tenancy
1 parent 8667344 commit bc38580

20 files changed

+610
-148
lines changed

.travis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ install: npm install
55
script: npm test -- --saucelabs
66
before_install:
77
- export CHROME_BIN=/usr/bin/google-chrome
8-
- export DISPLAY=:99.0
9-
- sh -e /etc/init.d/xvfb start
108
- sudo apt-get update
119
- sudo apt-get install -y libappindicator1 fonts-liberation
1210
- wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
1311
- sudo dpkg -i google-chrome*.deb
1412
- unset _JAVA_OPTIONS
13+
services:
14+
- xvfb
1515
addons:
1616
sauce_connect: true

README.md

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,17 @@ Localized versions of the widget are available through the CDN. To use a localiz
7070
localized JS library instead of the default library:
7171

7272
```html
73-
<script src="https://www.gstatic.com/firebasejs/ui/4.1.0/firebase-ui-auth__{LANGUAGE_CODE}.js"></script>
74-
<link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.1.0/firebase-ui-auth.css" />
73+
<script src="https://www.gstatic.com/firebasejs/ui/4.2.0/firebase-ui-auth__{LANGUAGE_CODE}.js"></script>
74+
<link type="text/css" rel="stylesheet" href="https://www.gstatic.com/firebasejs/ui/4.2.0/firebase-ui-auth.css" />
7575
```
7676

7777
where `{LANGUAGE_CODE}` is replaced by the code of the language you want. For example, the French
7878
version of the library is available at
79-
`https://www.gstatic.com/firebasejs/ui/4.1.0/firebase-ui-auth__fr.js`. The list of available
79+
`https://www.gstatic.com/firebasejs/ui/4.2.0/firebase-ui-auth__fr.js`. The list of available
8080
languages and their respective language codes can be found at [LANGUAGES.md](LANGUAGES.md).
8181

8282
Right-to-left languages also require the right-to-left version of the stylesheet, available at
83-
`https://www.gstatic.com/firebasejs/ui/4.1.0/firebase-ui-auth-rtl.css`, instead of the default
83+
`https://www.gstatic.com/firebasejs/ui/4.2.0/firebase-ui-auth-rtl.css`, instead of the default
8484
stylesheet. The supported right-to-left languages are Arabic (ar), Farsi (fa), and Hebrew (iw).
8585

8686
### Option 2: npm Module
@@ -1449,6 +1449,42 @@ ui.start('#firebaseui-auth-container', {
14491449
});
14501450
```
14511451
1452+
### Multi-tenancy support
1453+
1454+
For [GCIP](https://cloud.google.com/identity-platform) customers, you can build a
1455+
tenant-specific sign-in page with FirebaseUI. Make sure you've enabled
1456+
multi-tenancy for your project and configured your tenants. See the
1457+
[Multi-tenancy quickstart](https://cloud.google.com/identity-platform/docs/quickstart-multi-tenancy)
1458+
to learn how.
1459+
1460+
This feature requires [firebase](https://www.npmjs.com/package/firebase) version 6.6.0 or higher.
1461+
1462+
1463+
To use FirebaseUI with multi-tenancy, you need to set the tenant ID on the
1464+
Auth instance being passed to FirebaseUI before calling `ui.start()`.
1465+
1466+
```javascript
1467+
// The Firebase Auth instance.
1468+
var auth = firebase.auth();
1469+
// Initialize FirebaseUI.
1470+
var ui = new firebaseui.auth.AuthUI(auth);
1471+
// Set the tenant ID on Auth instance.
1472+
auth.tenantId = selectedTenantId;
1473+
// Start the sign-in flow in selected tenant.
1474+
// All sign-in attempts will now use this tenant ID.
1475+
ui.start('#firebaseui-auth-container', selectedTenantConfig);
1476+
```
1477+
1478+
FirebaseUI only handles the sign-in flows for you, you will still need to build
1479+
your own UI to let the end users select a tenant to sign in with.
1480+
You can refer to the example in this
1481+
[guide](https://cloud.google.com/identity-platform/docs/multi-tenancy-ui).
1482+
1483+
There is also a
1484+
[quickstart](https://github.com/firebase/quickstart-js/blob/master/auth/multi-tenant-ui.html)
1485+
app available to demonstrate how to build a single sign-in page with the
1486+
FirebaseUI for two tenants which have different sets of identity providers enabled.
1487+
14521488
14531489
## Customizing FirebaseUI for authentication
14541490

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
feature - Added multi-tenancy support for Google Cloud's Identity Platform developers.

javascript/utils/account_test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ goog.provide('firebaseui.auth.AccountTest');
2121
goog.require('firebaseui.auth.Account');
2222
goog.require('goog.testing.jsunit');
2323

24-
goog.setTestOnly('firebaseui.auth.idpTest');
24+
goog.setTestOnly('firebaseui.auth.AccountTest');
2525

2626

2727
var account = new firebaseui.auth.Account(

javascript/utils/actioncodeurlbuilder.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ firebaseui.auth.ActionCodeUrlBuilder.Parameter = {
6565
MODE: 'mode',
6666
OOB_CODE: 'oobCode',
6767
PROVIDER_ID: 'ui_pid',
68-
SESSION_ID: 'ui_sid'
68+
SESSION_ID: 'ui_sid',
69+
TENANT_ID: 'tenantId'
6970
};
7071

7172

@@ -112,6 +113,30 @@ firebaseui.auth.ActionCodeUrlBuilder.prototype.getApiKey = function() {
112113
};
113114

114115

116+
/**
117+
* Sets the tenant ID of the tenant project.
118+
* @param {?string} tenantId The tenant ID to be set.
119+
*/
120+
firebaseui.auth.ActionCodeUrlBuilder.prototype.setTenantId =
121+
function(tenantId) {
122+
if (tenantId) {
123+
this.uri_.setParameterValue(
124+
firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID,
125+
tenantId);
126+
} else {
127+
this.uri_.removeParameter(
128+
firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID);
129+
}
130+
};
131+
132+
133+
/** @return {?string} The tenant ID if available. */
134+
firebaseui.auth.ActionCodeUrlBuilder.prototype.getTenantId = function() {
135+
return this.uri_.getParameterValue(
136+
firebaseui.auth.ActionCodeUrlBuilder.Parameter.TENANT_ID) || null;
137+
};
138+
139+
115140
/**
116141
* Defines whether to force same device flow.
117142
* @param {?boolean} forceSameDevice Whether to force same device flow.

javascript/utils/actioncodeurlbuilder_test.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,15 @@ function testActionCodeUrlBuilder_outgoing() {
3636
assertNull(builder.getOobCode());
3737
assertNull(builder.getMode());
3838
assertNull(builder.getApiKey());
39+
assertNull(builder.getTenantId());
3940
assertEquals(url, builder.toString());
4041

4142
// Set new parameters.
4243
builder.setAnonymousUid('ANONYMOUS_UID');
4344
builder.setForceSameDevice(true);
4445
builder.setSessionId('SESSION_ID');
4546
builder.setProviderId('PROVIDER_ID');
47+
builder.setTenantId('TENANT_ID');
4648

4749
// Confirm new parameters.
4850
assertEquals('ANONYMOUS_UID', builder.getAnonymousUid());
@@ -52,12 +54,14 @@ function testActionCodeUrlBuilder_outgoing() {
5254
assertNull(builder.getOobCode());
5355
assertNull(builder.getMode());
5456
assertNull(builder.getApiKey());
57+
assertEquals('TENANT_ID', builder.getTenantId());
5558
assertEquals(
5659
'https://www.example.com/path/api?a=1&b=2&' +
5760
'ui_auid=ANONYMOUS_UID&' +
5861
'ui_sd=1&' +
5962
'ui_sid=SESSION_ID&' +
60-
'ui_pid=PROVIDER_ID' +
63+
'ui_pid=PROVIDER_ID&' +
64+
'tenantId=TENANT_ID' +
6165
'#c=2',
6266
builder.toString());
6367
}
@@ -70,6 +74,7 @@ function testActionCodeUrlBuilder_incoming() {
7074
'https://www.example.com/path/api?' +
7175
// Incoming link would also have API key, mode and oobCode fields.
7276
'apiKey=API_KEY&mode=signIn&oobCode=EMAIL_ACTION_CODE&' +
77+
'tenantId=TENANT_ID&' +
7378
'ui_auid=ANONYMOUS_UID&' +
7479
'ui_sd=1&' +
7580
'ui_sid=SESSION_ID&' +
@@ -83,12 +88,14 @@ function testActionCodeUrlBuilder_incoming() {
8388
assertEquals('EMAIL_ACTION_CODE', builder.getOobCode());
8489
assertEquals('signIn', builder.getMode());
8590
assertEquals('API_KEY', builder.getApiKey());
91+
assertEquals('TENANT_ID', builder.getTenantId());
8692

8793
// Clear all values.
8894
builder.setAnonymousUid(null);
8995
builder.setForceSameDevice(null);
9096
builder.setSessionId(null);
9197
builder.setProviderId(null);
98+
builder.setTenantId(null);
9299
// Confirm updated URL has relevant parameters cleared.
93100
assertEquals(url, builder.toString());
94101

@@ -97,6 +104,7 @@ function testActionCodeUrlBuilder_incoming() {
97104
builder.setForceSameDevice(false);
98105
builder.setSessionId('SESSION_ID2');
99106
builder.setProviderId('PROVIDER_ID2');
107+
builder.setTenantId('TENANT_ID2');
100108

101109
// Confirm new expected values.
102110
assertEquals('ANONYMOUS_UID2', builder.getAnonymousUid());
@@ -110,7 +118,8 @@ function testActionCodeUrlBuilder_incoming() {
110118
'ui_auid=ANONYMOUS_UID2&' +
111119
'ui_sd=0&' +
112120
'ui_sid=SESSION_ID2&' +
113-
'ui_pid=PROVIDER_ID2' +
121+
'ui_pid=PROVIDER_ID2&' +
122+
'tenantId=TENANT_ID2' +
114123
'#c=2',
115124
builder.toString());
116125
}
@@ -126,6 +135,7 @@ function testActionCodeUrlBuilder_clearState() {
126135
'ui_sd=1&' +
127136
'ui_sid=SESSION_ID&' +
128137
'ui_pid=PROVIDER_ID&' +
138+
'tenantId=TENANT_ID&' +
129139
'lang=en&a=1&b=2#c=2');
130140

131141
// Confirm expected values parsed from url.
@@ -136,6 +146,7 @@ function testActionCodeUrlBuilder_clearState() {
136146
assertEquals('EMAIL_ACTION_CODE', builder.getOobCode());
137147
assertEquals('signIn', builder.getMode());
138148
assertEquals('API_KEY', builder.getApiKey());
149+
assertEquals('TENANT_ID', builder.getTenantId());
139150

140151
// Clear state of URL from anything related to email action codes.
141152
builder.clearState();
@@ -148,5 +159,6 @@ function testActionCodeUrlBuilder_clearState() {
148159
assertNull(builder.getOobCode());
149160
assertNull(builder.getMode());
150161
assertNull(builder.getApiKey());
162+
assertNull(builder.getTenantId());
151163
assertEquals(url, builder.toString());
152164
}

javascript/utils/redirectstatus.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2019 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
/**
16+
* @fileoverview Defines the redirect status object. This is used to save the
17+
* state of a sign-in attempt before a redirect operation. It holds the
18+
* original tenant ID used to sign in with and also indicates there is a pending
19+
* redirect operation to be resolved.
20+
*/
21+
22+
goog.provide('firebaseui.auth.RedirectStatus');
23+
24+
25+
26+
/**
27+
* The redirect status. It indicates there is a pending redirect operation to be
28+
* resolved.
29+
* @param {?string=} opt_tenantId The optional tenant ID.
30+
* @constructor
31+
*/
32+
firebaseui.auth.RedirectStatus = function(opt_tenantId) {
33+
/** @const @private {?string} The tenant ID. */
34+
this.tenantId_ = opt_tenantId || null;
35+
};
36+
37+
38+
/** @return {?string} The tenant ID. */
39+
firebaseui.auth.RedirectStatus.prototype.getTenantId = function() {
40+
return this.tenantId_;
41+
};
42+
43+
44+
/**
45+
* @return {!Object} The plain object representation of redirect status.
46+
*/
47+
firebaseui.auth.RedirectStatus.prototype.toPlainObject = function() {
48+
return {
49+
'tenantId': this.tenantId_
50+
};
51+
};
52+
53+
54+
/**
55+
* @param {?Object} response The plain object presentation of a potential
56+
* redirect status object.
57+
* @return {?firebaseui.auth.RedirectStatus} The redirect status representation
58+
* of the provided object.
59+
*/
60+
firebaseui.auth.RedirectStatus.fromPlainObject = function(response) {
61+
if (response && typeof response['tenantId'] !== 'undefined') {
62+
return new firebaseui.auth.RedirectStatus(response['tenantId']);
63+
}
64+
return null;
65+
};
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2019 Google Inc. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
/**
16+
* @fileoverview Tests for redirectstatus.js.
17+
*/
18+
19+
goog.provide('firebaseui.auth.RedirectStatusTest');
20+
21+
goog.require('firebaseui.auth.RedirectStatus');
22+
goog.require('goog.testing.jsunit');
23+
24+
goog.setTestOnly('firebaseui.auth.RedirectStatusTest');
25+
26+
27+
var redirectStatus1 = new firebaseui.auth.RedirectStatus('TENANT_ID');
28+
var redirectStatus2 = new firebaseui.auth.RedirectStatus();
29+
var obj1 = {
30+
'tenantId': 'TENANT_ID'
31+
};
32+
var obj2 = {
33+
'tenantId': null
34+
};
35+
36+
37+
function testRedirectStatus() {
38+
assertEquals('TENANT_ID', redirectStatus1.getTenantId());
39+
assertNull(redirectStatus2.getTenantId());
40+
}
41+
42+
43+
function testToPlainObject() {
44+
assertObjectEquals(obj1, redirectStatus1.toPlainObject());
45+
assertObjectEquals(obj2, redirectStatus2.toPlainObject());
46+
}
47+
48+
49+
function testFromPlainObject() {
50+
assertObjectEquals(redirectStatus1,
51+
firebaseui.auth.RedirectStatus.fromPlainObject(obj1));
52+
assertObjectEquals(redirectStatus2,
53+
firebaseui.auth.RedirectStatus.fromPlainObject(obj2));
54+
assertNull(firebaseui.auth.RedirectStatus.fromPlainObject({}));
55+
}

0 commit comments

Comments
 (0)