mirror of
https://github.com/bitwarden/browser.git
synced 2024-11-09 09:51:02 +01:00
switch to jslib auth service
This commit is contained in:
parent
f382f125be
commit
146254b646
@ -33,7 +33,7 @@ angular
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.loginPromise = authService.logIn(model.email, model.masterPassword, null, null);
|
$scope.loginPromise = authService.logIn(model.email, model.masterPassword);
|
||||||
|
|
||||||
$scope.loginPromise.then(function (response) {
|
$scope.loginPromise.then(function (response) {
|
||||||
if (response.twoFactor) {
|
if (response.twoFactor) {
|
||||||
|
@ -36,17 +36,17 @@ angular
|
|||||||
});
|
});
|
||||||
|
|
||||||
var constants = constantsService;
|
var constants = constantsService;
|
||||||
var email = $stateParams.email;
|
var email = authService.email;
|
||||||
var masterPassword = $stateParams.masterPassword;
|
var masterPasswordHash = authService.masterPasswordHash;
|
||||||
var providers = $stateParams.providers;
|
var providers = authService.twoFactorProviders;
|
||||||
|
|
||||||
if (!email || !masterPassword || !providers) {
|
if (!email || !masterPasswordHash || !providers) {
|
||||||
$state.go('login');
|
$state.go('login');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.providerType = $stateParams.provider || $stateParams.provider === 0 ? $stateParams.provider :
|
$scope.providerType = $stateParams.provider || $stateParams.provider === 0 ? $stateParams.provider :
|
||||||
getDefaultProvider(providers);
|
authService.getDefaultTwoFactorProvider(platformUtilsService.supportsU2f($window));
|
||||||
$scope.twoFactorEmail = null;
|
$scope.twoFactorEmail = null;
|
||||||
$scope.token = null;
|
$scope.token = null;
|
||||||
$scope.constantsProvider = constants.twoFactorProvider;
|
$scope.constantsProvider = constants.twoFactorProvider;
|
||||||
@ -74,7 +74,7 @@ angular
|
|||||||
token = token.replace(' ', '');
|
token = token.replace(' ', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.loginPromise = authService.logIn(email, masterPassword, $scope.providerType, token, $scope.remember.checked);
|
$scope.loginPromise = authService.logInTwoFactor($scope.providerType, token, $scope.remember.checked);
|
||||||
$scope.loginPromise.then(function () {
|
$scope.loginPromise.then(function () {
|
||||||
$analytics.eventTrack('Logged In From Two-step');
|
$analytics.eventTrack('Logged In From Two-step');
|
||||||
$state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true }).then(function () {
|
$state.go('tabs.vault', { animation: 'in-slide-left', syncOnLoad: true }).then(function () {
|
||||||
@ -97,9 +97,7 @@ angular
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var key = cryptoService.makeKey(masterPassword, email);
|
var request = new TwoFactorEmailRequest(email, masterPasswordHash);
|
||||||
cryptoService.hashPassword(masterPassword, key).then(function (hash) {
|
|
||||||
var request = new TwoFactorEmailRequest(email, hash);
|
|
||||||
apiService.postTwoFactorEmail(request, function () {
|
apiService.postTwoFactorEmail(request, function () {
|
||||||
if (doToast) {
|
if (doToast) {
|
||||||
toastr.success('Verification email sent to ' + $scope.twoFactorEmail + '.');
|
toastr.success('Verification email sent to ' + $scope.twoFactorEmail + '.');
|
||||||
@ -107,15 +105,11 @@ angular
|
|||||||
}, function () {
|
}, function () {
|
||||||
toastr.error('Could not send verification email.');
|
toastr.error('Could not send verification email.');
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.anotherMethod = function () {
|
$scope.anotherMethod = function () {
|
||||||
$state.go('twoFactorMethods', {
|
$state.go('twoFactorMethods', {
|
||||||
animation: 'in-slide-up',
|
animation: 'in-slide-up',
|
||||||
email: email,
|
|
||||||
masterPassword: masterPassword,
|
|
||||||
providers: providers,
|
|
||||||
provider: $scope.providerType
|
provider: $scope.providerType
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -140,25 +134,6 @@ angular
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getDefaultProvider(twoFactorProviders) {
|
|
||||||
var keys = Object.keys(twoFactorProviders);
|
|
||||||
var providerType = null;
|
|
||||||
var providerPriority = -1;
|
|
||||||
for (var i = 0; i < keys.length; i++) {
|
|
||||||
var provider = $filter('filter')(constants.twoFactorProviderInfo, { type: keys[i], active: true });
|
|
||||||
if (provider.length && provider[0].priority > providerPriority) {
|
|
||||||
if (provider[0].type == constants.twoFactorProvider.u2f && (typeof $window.u2f === 'undefined') &&
|
|
||||||
!platformUtilsService.isChrome() && !platformUtilsService.isOpera()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
providerType = provider[0].type;
|
|
||||||
providerPriority = provider[0].priority;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return providerType === null ? null : parseInt(providerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
u2f.stop();
|
u2f.stop();
|
||||||
u2f.cleanup();
|
u2f.cleanup();
|
||||||
@ -169,9 +144,8 @@ angular
|
|||||||
codeInput.focus();
|
codeInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
var params;
|
var params = providers.get($scope.providerType);
|
||||||
if ($scope.providerType === constants.twoFactorProvider.duo) {
|
if ($scope.providerType === constants.twoFactorProvider.duo) {
|
||||||
params = providers[constants.twoFactorProvider.duo];
|
|
||||||
if (platformUtilsService.isSafari()) {
|
if (platformUtilsService.isSafari()) {
|
||||||
var tab = BrowserApi.createNewTab(BrowserApi.getAssetUrl('2fa/index.html'));
|
var tab = BrowserApi.createNewTab(BrowserApi.getAssetUrl('2fa/index.html'));
|
||||||
var tabToSend = BrowserApi.makeTabObject(tab);
|
var tabToSend = BrowserApi.makeTabObject(tab);
|
||||||
@ -200,7 +174,6 @@ angular
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ($scope.providerType === constants.twoFactorProvider.u2f) {
|
else if ($scope.providerType === constants.twoFactorProvider.u2f) {
|
||||||
params = providers[constants.twoFactorProvider.u2f];
|
|
||||||
var challenges = JSON.parse(params.Challenges);
|
var challenges = JSON.parse(params.Challenges);
|
||||||
|
|
||||||
u2f.init({
|
u2f.init({
|
||||||
@ -213,7 +186,6 @@ angular
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if ($scope.providerType === constants.twoFactorProvider.email) {
|
else if ($scope.providerType === constants.twoFactorProvider.email) {
|
||||||
params = providers[constants.twoFactorProvider.email];
|
|
||||||
$scope.twoFactorEmail = params.Email;
|
$scope.twoFactorEmail = params.Email;
|
||||||
|
|
||||||
if (!platformUtilsService.isSafari() && BrowserApi.isPopupOpen() &&
|
if (!platformUtilsService.isSafari() && BrowserApi.isPopupOpen() &&
|
||||||
@ -230,12 +202,12 @@ angular
|
|||||||
BrowserApi.createNewTab('/popup/index.html?uilocation=tab#!/login', true);
|
BrowserApi.createNewTab('/popup/index.html?uilocation=tab#!/login', true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (Object.keys(providers).length > 1) {
|
else if (providers.size > 1) {
|
||||||
$scope.sendEmail(false);
|
$scope.sendEmail(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (Object.keys(providers).length > 1) {
|
else if (providers.size > 1) {
|
||||||
$scope.sendEmail(false);
|
$scope.sendEmail(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,50 +2,41 @@ angular
|
|||||||
.module('bit.accounts')
|
.module('bit.accounts')
|
||||||
|
|
||||||
.controller('accountsTwoFactorMethodsController', function ($scope, $state, $stateParams, constantsService,
|
.controller('accountsTwoFactorMethodsController', function ($scope, $state, $stateParams, constantsService,
|
||||||
utilsService, i18nService, $analytics, platformUtilsService) {
|
utilsService, i18nService, $analytics, platformUtilsService, authService, $window) {
|
||||||
$scope.i18n = i18nService;
|
$scope.i18n = i18nService;
|
||||||
|
|
||||||
var constants = constantsService;
|
var constants = constantsService;
|
||||||
var masterPassword = $stateParams.masterPassword;
|
var providers = authService.twoFactorProviders;
|
||||||
var email = $stateParams.email;
|
|
||||||
var providers = $stateParams.providers;
|
|
||||||
var provider = $stateParams.provider;
|
var provider = $stateParams.provider;
|
||||||
|
|
||||||
$scope.providers = [];
|
$scope.providers = [];
|
||||||
|
|
||||||
if (providers.hasOwnProperty(constants.twoFactorProvider.authenticator)) {
|
if (providers.get(constants.twoFactorProvider.authenticator)) {
|
||||||
add(constants.twoFactorProvider.authenticator);
|
add(constants.twoFactorProvider.authenticator);
|
||||||
}
|
}
|
||||||
if (providers.hasOwnProperty(constants.twoFactorProvider.yubikey)) {
|
if (providers.get(constants.twoFactorProvider.yubikey)) {
|
||||||
add(constants.twoFactorProvider.yubikey);
|
add(constants.twoFactorProvider.yubikey);
|
||||||
}
|
}
|
||||||
if (providers.hasOwnProperty(constants.twoFactorProvider.email)) {
|
if (providers.get(constants.twoFactorProvider.email)) {
|
||||||
add(constants.twoFactorProvider.email);
|
add(constants.twoFactorProvider.email);
|
||||||
}
|
}
|
||||||
if (providers.hasOwnProperty(constants.twoFactorProvider.duo)) {
|
if (providers.get(constants.twoFactorProvider.duo)) {
|
||||||
add(constants.twoFactorProvider.duo);
|
add(constants.twoFactorProvider.duo);
|
||||||
}
|
}
|
||||||
if (providers.hasOwnProperty(constants.twoFactorProvider.u2f) &&
|
if (providers.get(constants.twoFactorProvider.u2f) && platformUtilsService.supportsU2f($window)) {
|
||||||
(platformUtilsService.isChrome() || platformUtilsService.isOpera())) {
|
|
||||||
add(constants.twoFactorProvider.u2f);
|
add(constants.twoFactorProvider.u2f);
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.choose = function (provider) {
|
$scope.choose = function (p) {
|
||||||
$state.go('twoFactor', {
|
$state.go('twoFactor', {
|
||||||
animation: 'out-slide-down',
|
animation: 'out-slide-down',
|
||||||
email: email,
|
provider: p.type
|
||||||
masterPassword: masterPassword,
|
|
||||||
providers: providers,
|
|
||||||
provider: provider.type
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.cancel = function () {
|
$scope.cancel = function () {
|
||||||
$state.go('twoFactor', {
|
$state.go('twoFactor', {
|
||||||
animation: 'out-slide-down',
|
animation: 'out-slide-down',
|
||||||
email: email,
|
|
||||||
masterPassword: masterPassword,
|
|
||||||
providers: providers,
|
|
||||||
provider: provider
|
provider: provider
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -84,14 +84,14 @@ angular
|
|||||||
controller: 'accountsLoginTwoFactorController',
|
controller: 'accountsLoginTwoFactorController',
|
||||||
template: require('./accounts/views/accountsLoginTwoFactor.html'),
|
template: require('./accounts/views/accountsLoginTwoFactor.html'),
|
||||||
data: { authorize: false },
|
data: { authorize: false },
|
||||||
params: { animation: null, email: null, masterPassword: null, providers: null, provider: null }
|
params: { animation: null, provider: null }
|
||||||
})
|
})
|
||||||
.state('twoFactorMethods', {
|
.state('twoFactorMethods', {
|
||||||
url: '/two-factor-methods',
|
url: '/two-factor-methods',
|
||||||
controller: 'accountsTwoFactorMethodsController',
|
controller: 'accountsTwoFactorMethodsController',
|
||||||
template: require('./accounts/views/accountsTwoFactorMethods.html'),
|
template: require('./accounts/views/accountsTwoFactorMethods.html'),
|
||||||
data: { authorize: false },
|
data: { authorize: false },
|
||||||
params: { animation: null, email: null, masterPassword: null, providers: null, provider: null }
|
params: { animation: null, provider: null }
|
||||||
})
|
})
|
||||||
.state('register', {
|
.state('register', {
|
||||||
url: '/register',
|
url: '/register',
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { BrowserApi } from '../../../browser/browserApi';
|
import { BrowserApi } from '../../../browser/browserApi';
|
||||||
|
|
||||||
import { AuthService } from '../services/auth.service';
|
import { AuthService } from 'jslib/abstractions/auth.service';
|
||||||
|
|
||||||
import { UtilsService } from 'jslib/abstractions/utils.service';
|
import { UtilsService } from 'jslib/abstractions/utils.service';
|
||||||
|
|
||||||
export class MainController implements ng.IController {
|
export class MainController implements ng.IController {
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
import { DeviceRequest } from 'jslib/models/request/deviceRequest';
|
|
||||||
import { TokenRequest } from 'jslib/models/request/tokenRequest';
|
|
||||||
|
|
||||||
import { ConstantsService } from 'jslib/services/constants.service';
|
|
||||||
|
|
||||||
import { ApiService } from 'jslib/abstractions/api.service';
|
|
||||||
import { AppIdService } from 'jslib/abstractions/appId.service';
|
|
||||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
|
||||||
import { TokenService } from 'jslib/abstractions/token.service';
|
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
|
||||||
|
|
||||||
export class AuthService {
|
|
||||||
constructor(public cryptoService: CryptoService, public apiService: ApiService, public userService: UserService,
|
|
||||||
public tokenService: TokenService, public $rootScope: any, public appIdService: AppIdService,
|
|
||||||
public platformUtilsService: PlatformUtilsService, public constantsService: ConstantsService,
|
|
||||||
public messagingService: MessagingService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
async logIn(email: string, masterPassword: string, twoFactorProvider?: number,
|
|
||||||
twoFactorToken?: string, remember?: boolean) {
|
|
||||||
email = email.toLowerCase();
|
|
||||||
|
|
||||||
const key = this.cryptoService.makeKey(masterPassword, email);
|
|
||||||
const appId = await this.appIdService.getAppId();
|
|
||||||
const storedTwoFactorToken = await this.tokenService.getTwoFactorToken(email);
|
|
||||||
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
|
|
||||||
|
|
||||||
const deviceRequest = new DeviceRequest(appId, this.platformUtilsService);
|
|
||||||
|
|
||||||
let request: TokenRequest;
|
|
||||||
|
|
||||||
if (twoFactorToken != null && twoFactorProvider != null) {
|
|
||||||
request = new TokenRequest(email, hashedPassword, twoFactorProvider, twoFactorToken, remember,
|
|
||||||
deviceRequest);
|
|
||||||
} else if (storedTwoFactorToken) {
|
|
||||||
request = new TokenRequest(email, hashedPassword, this.constantsService.twoFactorProvider.remember,
|
|
||||||
storedTwoFactorToken, false, deviceRequest);
|
|
||||||
} else {
|
|
||||||
request = new TokenRequest(email, hashedPassword, null, null, false, deviceRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await this.apiService.postIdentityToken(request);
|
|
||||||
if (!response) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.accessToken) {
|
|
||||||
// two factor required
|
|
||||||
return {
|
|
||||||
twoFactor: true,
|
|
||||||
twoFactorProviders: response,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.twoFactorToken) {
|
|
||||||
this.tokenService.setTwoFactorToken(response.twoFactorToken, email);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.tokenService.setTokens(response.accessToken, response.refreshToken);
|
|
||||||
await this.cryptoService.setKey(key);
|
|
||||||
await this.cryptoService.setKeyHash(hashedPassword);
|
|
||||||
await this.userService.setUserIdAndEmail(this.tokenService.getUserId(), this.tokenService.getEmail());
|
|
||||||
await this.cryptoService.setEncKey(response.key);
|
|
||||||
await this.cryptoService.setEncPrivateKey(response.privateKey);
|
|
||||||
|
|
||||||
this.messagingService.send('loggedIn');
|
|
||||||
return {
|
|
||||||
twoFactor: false,
|
|
||||||
twoFactorProviders: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
logOut(callback: Function) {
|
|
||||||
this.$rootScope.vaultCiphers = null;
|
|
||||||
this.$rootScope.vaultFolders = null;
|
|
||||||
this.$rootScope.vaultCollections = null;
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthService.$inject = ['cryptoService', 'apiService', 'userService', 'tokenService', '$rootScope', 'appIdService',
|
|
||||||
'platformUtilsService', 'constantsService', 'messagingService'];
|
|
@ -9,6 +9,7 @@ import { CollectionService } from 'jslib/abstractions/collection.service';
|
|||||||
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
import { EnvironmentService } from 'jslib/abstractions/environment.service';
|
||||||
import { FolderService } from 'jslib/abstractions/folder.service';
|
import { FolderService } from 'jslib/abstractions/folder.service';
|
||||||
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { LockService } from 'jslib/abstractions/lock.service';
|
import { LockService } from 'jslib/abstractions/lock.service';
|
||||||
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
import { PasswordGenerationService } from 'jslib/abstractions/passwordGeneration.service';
|
||||||
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
@ -43,6 +44,7 @@ export const platformUtilsService = getBackgroundService<PlatformUtilsService>('
|
|||||||
export const utilsService = getBackgroundService<UtilsService>('utilsService');
|
export const utilsService = getBackgroundService<UtilsService>('utilsService');
|
||||||
export const appIdService = getBackgroundService<AppIdService>('appIdService');
|
export const appIdService = getBackgroundService<AppIdService>('appIdService');
|
||||||
export const i18nService = getBackgroundService<any>('i18nService');
|
export const i18nService = getBackgroundService<any>('i18nService');
|
||||||
|
export const i18n2Service = getBackgroundService<I18nService>('i18n2Service');
|
||||||
export const constantsService = getBackgroundService<ConstantsService>('constantsService');
|
export const constantsService = getBackgroundService<ConstantsService>('constantsService');
|
||||||
export const settingsService = getBackgroundService<SettingsService>('settingsService');
|
export const settingsService = getBackgroundService<SettingsService>('settingsService');
|
||||||
export const lockService = getBackgroundService<LockService>('lockService');
|
export const lockService = getBackgroundService<LockService>('lockService');
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
import * as angular from 'angular';
|
import * as angular from 'angular';
|
||||||
import { AuthService } from './auth.service';
|
|
||||||
import * as backgroundServices from './background.service';
|
import * as backgroundServices from './background.service';
|
||||||
import { PopupUtilsService } from './popupUtils.service';
|
import { PopupUtilsService } from './popupUtils.service';
|
||||||
import { StateService } from './state.service';
|
import { StateService } from './state.service';
|
||||||
import { ValidationService } from './validation.service';
|
import { ValidationService } from './validation.service';
|
||||||
|
|
||||||
|
import { AuthService } from 'jslib/services/auth.service';
|
||||||
|
|
||||||
import BrowserMessagingService from '../../../services/browserMessaging.service';
|
import BrowserMessagingService from '../../../services/browserMessaging.service';
|
||||||
|
|
||||||
const messagingService = new BrowserMessagingService(backgroundServices.platformUtilsService());
|
const messagingService = new BrowserMessagingService(backgroundServices.platformUtilsService());
|
||||||
|
const authService = new AuthService(backgroundServices.cryptoService(), backgroundServices.apiService(),
|
||||||
|
backgroundServices.userService(), backgroundServices.tokenService(), backgroundServices.appIdService(),
|
||||||
|
backgroundServices.i18n2Service(), backgroundServices.platformUtilsService(),
|
||||||
|
backgroundServices.constantsService(), messagingService);
|
||||||
|
authService.init();
|
||||||
|
|
||||||
export default angular
|
export default angular
|
||||||
.module('bit.services', ['toastr'])
|
.module('bit.services', ['toastr'])
|
||||||
.service('stateService', StateService)
|
.service('stateService', StateService)
|
||||||
.service('validationService', ValidationService)
|
.service('validationService', ValidationService)
|
||||||
.service('authService', AuthService)
|
|
||||||
.service('popupUtilsService', PopupUtilsService)
|
.service('popupUtilsService', PopupUtilsService)
|
||||||
|
|
||||||
|
.factory('authService', () => authService)
|
||||||
.factory('messagingService', () => messagingService)
|
.factory('messagingService', () => messagingService)
|
||||||
.factory('storageService', backgroundServices.storageService)
|
.factory('storageService', backgroundServices.storageService)
|
||||||
.factory('tokenService', backgroundServices.tokenService)
|
.factory('tokenService', backgroundServices.tokenService)
|
||||||
|
@ -152,6 +152,14 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
|||||||
return BrowserApi.getApplicationVersion();
|
return BrowserApi.getApplicationVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
supportsU2f(win: Window): boolean {
|
||||||
|
if (win != null && (win as any).u2f !== 'undefined') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.isChrome() || this.isOpera();
|
||||||
|
}
|
||||||
|
|
||||||
private sidebarViewName(): string {
|
private sidebarViewName(): string {
|
||||||
if ((window as any).chrome.sidebarAction && this.isFirefox()) {
|
if ((window as any).chrome.sidebarAction && this.isFirefox()) {
|
||||||
return 'sidebar';
|
return 'sidebar';
|
||||||
|
Loading…
Reference in New Issue
Block a user