mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
duo 2fa config and login with web sdk
This commit is contained in:
@@ -177,6 +177,10 @@ gulp.task('lib', ['clean:lib'], function () {
|
|||||||
paths.npmDir + 'angulartics/src/angulartics.js'
|
paths.npmDir + 'angulartics/src/angulartics.js'
|
||||||
],
|
],
|
||||||
dest: paths.libDir + 'angulartics'
|
dest: paths.libDir + 'angulartics'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: paths.npmDir + 'duo_web_sdk/index.js',
|
||||||
|
dest: paths.libDir + 'duo'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"browserify": "14.1.0",
|
"browserify": "14.1.0",
|
||||||
"vinyl-source-stream": "1.1.0",
|
"vinyl-source-stream": "1.1.0",
|
||||||
"gulp-derequire": "2.1.0",
|
"gulp-derequire": "2.1.0",
|
||||||
"exposify": "0.5.0"
|
"exposify": "0.5.0",
|
||||||
|
"duo_web_sdk": "git+https://github.com/duosecurity/duo_web_sdk.git"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ angular
|
|||||||
$state.go('frontend.login.twoFactor', { returnState: returnState }).then(function () {
|
$state.go('frontend.login.twoFactor', { returnState: returnState }).then(function () {
|
||||||
$timeout(function () {
|
$timeout(function () {
|
||||||
$("#code").focus();
|
$("#code").focus();
|
||||||
|
init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -90,6 +91,7 @@ angular
|
|||||||
$scope.twoFactorProvider = provider;
|
$scope.twoFactorProvider = provider;
|
||||||
$timeout(function () {
|
$timeout(function () {
|
||||||
$("#code").focus();
|
$("#code").focus();
|
||||||
|
init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -102,4 +104,19 @@ angular
|
|||||||
$state.go('backend.user.vault');
|
$state.go('backend.user.vault');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
if ($scope.twoFactorProvider === constants.twoFactorProvider.duo) {
|
||||||
|
var params = $scope.twoFactorProviders[constants.twoFactorProvider.duo];
|
||||||
|
|
||||||
|
Duo.init({
|
||||||
|
host: params.Host,
|
||||||
|
sig_request: params.Signature,
|
||||||
|
submit_callback: function (theForm) {
|
||||||
|
var response = $(theForm).find('input[name="sig_response"]').val();
|
||||||
|
$scope.twoFactor(response);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -57,3 +57,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div ng-if="twoFactorProvider === 2">
|
||||||
|
<p class="login-box-msg">
|
||||||
|
Complete logging in with Duo.
|
||||||
|
</p>
|
||||||
|
<form name="twoFactorForm" ng-submit="twoFactorForm.$valid && twoFactor(token)" api-form="twoFactorPromise">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="twoFactorForm.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in twoFactorForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="duoFrameWrapper">
|
||||||
|
<iframe id="duo_iframe"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-7">
|
||||||
|
<a stop-click href="#" ng-click="anotherMethod()">Use another method?</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-5">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block btn-flat" ng-disabled="twoFactorForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="twoFactorForm.$loading"></i>Log In
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -114,11 +114,13 @@
|
|||||||
_service.twoFactor = $resource(_apiUri + '/two-factor', {}, {
|
_service.twoFactor = $resource(_apiUri + '/two-factor', {}, {
|
||||||
list: { method: 'GET', params: {} },
|
list: { method: 'GET', params: {} },
|
||||||
getEmail: { url: _apiUri + '/two-factor/get-email', method: 'POST', params: {} },
|
getEmail: { url: _apiUri + '/two-factor/get-email', method: 'POST', params: {} },
|
||||||
|
getDuo: { url: _apiUri + '/two-factor/get-duo', method: 'POST', params: {} },
|
||||||
getAuthenticator: { url: _apiUri + '/two-factor/get-authenticator', method: 'POST', params: {} },
|
getAuthenticator: { url: _apiUri + '/two-factor/get-authenticator', method: 'POST', params: {} },
|
||||||
getYubi: { url: _apiUri + '/two-factor/get-yubikey', method: 'POST', params: {} },
|
getYubi: { url: _apiUri + '/two-factor/get-yubikey', method: 'POST', params: {} },
|
||||||
sendEmail: { url: _apiUri + '/two-factor/send-email', method: 'POST', params: {} },
|
sendEmail: { url: _apiUri + '/two-factor/send-email', method: 'POST', params: {} },
|
||||||
putEmail: { url: _apiUri + '/two-factor/email', method: 'POST', params: {} },
|
putEmail: { url: _apiUri + '/two-factor/email', method: 'POST', params: {} },
|
||||||
putAuthenticator: { url: _apiUri + '/two-factor/authenticator', method: 'POST', params: {} },
|
putAuthenticator: { url: _apiUri + '/two-factor/authenticator', method: 'POST', params: {} },
|
||||||
|
putDuo: { url: _apiUri + '/two-factor/duo', method: 'POST', params: {} },
|
||||||
putYubi: { url: _apiUri + '/two-factor/yubikey', method: 'POST', params: {} },
|
putYubi: { url: _apiUri + '/two-factor/yubikey', method: 'POST', params: {} },
|
||||||
disable: { url: _apiUri + '/two-factor/disable', method: 'POST', params: {} },
|
disable: { url: _apiUri + '/two-factor/disable', method: 'POST', params: {} },
|
||||||
recover: { url: _apiUri + '/two-factor/recover', method: 'POST', params: {} },
|
recover: { url: _apiUri + '/two-factor/recover', method: 'POST', params: {} },
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
var _issuer = 'bitwarden',
|
var _issuer = 'bitwarden',
|
||||||
_profile = null,
|
_profile = null,
|
||||||
_masterPasswordHash
|
_masterPasswordHash
|
||||||
_key = null;
|
_key = null;
|
||||||
|
|
||||||
$scope.auth = function (model) {
|
$scope.auth = function (model) {
|
||||||
_masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
|
_masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
|
||||||
@@ -69,6 +69,7 @@
|
|||||||
}, function (response) {
|
}, function (response) {
|
||||||
$analytics.eventTrack('Disabled Two-step Authenticator');
|
$analytics.eventTrack('Disabled Two-step Authenticator');
|
||||||
toastr.success('Authenticator app has been disabled.');
|
toastr.success('Authenticator app has been disabled.');
|
||||||
|
$scope.enabled = response.Enabled;
|
||||||
$scope.close();
|
$scope.close();
|
||||||
}).$promise;
|
}).$promise;
|
||||||
}
|
}
|
||||||
@@ -86,6 +87,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.close = function () {
|
$scope.close = function () {
|
||||||
$uibModalInstance.close();
|
$uibModalInstance.close($scope.enabled);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -65,8 +65,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
authenticatorModal.result.then(function () {
|
authenticatorModal.result.then(function (enabled) {
|
||||||
|
provider.enabled = enabled;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if(provider.type === constants.twoFactorProvider.email) {
|
else if(provider.type === constants.twoFactorProvider.email) {
|
||||||
@@ -79,8 +79,8 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
emailModal.result.then(function () {
|
emailModal.result.then(function (enabled) {
|
||||||
|
provider.enabled = enabled;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (provider.type === constants.twoFactorProvider.yubikey) {
|
else if (provider.type === constants.twoFactorProvider.yubikey) {
|
||||||
@@ -93,8 +93,22 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
yubiModal.result.then(function () {
|
yubiModal.result.then(function (enabled) {
|
||||||
|
provider.enabled = enabled;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (provider.type === constants.twoFactorProvider.duo) {
|
||||||
|
var yubiModal = $uibModal.open({
|
||||||
|
animation: true,
|
||||||
|
templateUrl: 'app/settings/views/settingsTwoStepDuo.html',
|
||||||
|
controller: 'settingsTwoStepDuoController',
|
||||||
|
resolve: {
|
||||||
|
enabled: function () { return provider.enabled; }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
yubiModal.result.then(function (enabled) {
|
||||||
|
provider.enabled = enabled;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
75
src/app/settings/settingsTwoStepDuoController.js
Normal file
75
src/app/settings/settingsTwoStepDuoController.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
angular
|
||||||
|
.module('bit.settings')
|
||||||
|
|
||||||
|
.controller('settingsTwoStepDuoController', function ($scope, apiService, $uibModalInstance, cryptoService,
|
||||||
|
toastr, $analytics, constants) {
|
||||||
|
$analytics.eventTrack('settingsTwoStepDuoController', { category: 'Modal' });
|
||||||
|
var _masterPasswordHash;
|
||||||
|
|
||||||
|
$scope.updateModel = {
|
||||||
|
token: null,
|
||||||
|
host: null,
|
||||||
|
ikey: null,
|
||||||
|
skey: null
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.auth = function (model) {
|
||||||
|
_masterPasswordHash = cryptoService.hashPassword(model.masterPassword);
|
||||||
|
$scope.authPromise = apiService.twoFactor.getDuo({}, {
|
||||||
|
masterPasswordHash: _masterPasswordHash
|
||||||
|
}).$promise.then(function (apiResponse) {
|
||||||
|
processResult(apiResponse);
|
||||||
|
$scope.authed = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.submit = function (model) {
|
||||||
|
if ($scope.enabled) {
|
||||||
|
disable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(model);
|
||||||
|
};
|
||||||
|
|
||||||
|
function disable() {
|
||||||
|
if (!confirm('Are you sure you want to disable the Duo provider?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.submitPromise = apiService.twoFactor.disable({}, {
|
||||||
|
masterPasswordHash: _masterPasswordHash,
|
||||||
|
type: constants.twoFactorProvider.duo
|
||||||
|
}, function (response) {
|
||||||
|
$analytics.eventTrack('Disabled Two-step Duo');
|
||||||
|
toastr.success('Duo has been disabled.');
|
||||||
|
$scope.enabled = response.Enabled;
|
||||||
|
$scope.close();
|
||||||
|
}).$promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(model) {
|
||||||
|
$scope.submitPromise = apiService.twoFactor.putDuo({}, {
|
||||||
|
integrationKey: model.ikey,
|
||||||
|
secretKey: model.skey,
|
||||||
|
host: model.host,
|
||||||
|
masterPasswordHash: _masterPasswordHash
|
||||||
|
}, function (response) {
|
||||||
|
$analytics.eventTrack('Enabled Two-step Duo');
|
||||||
|
processResult(response);
|
||||||
|
}).$promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
function processResult(response) {
|
||||||
|
$scope.enabled = response.Enabled;
|
||||||
|
$scope.updateModel = {
|
||||||
|
ikey: response.IntegrationKey,
|
||||||
|
skey: response.SecretKey,
|
||||||
|
host: response.Host
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.close = function () {
|
||||||
|
$uibModalInstance.close($scope.enabled);
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
}, function (response) {
|
}, function (response) {
|
||||||
$analytics.eventTrack('Disabled Two-step Email');
|
$analytics.eventTrack('Disabled Two-step Email');
|
||||||
toastr.success('Email has been disabled.');
|
toastr.success('Email has been disabled.');
|
||||||
|
$scope.enabled = response.Enabled;
|
||||||
$scope.close();
|
$scope.close();
|
||||||
}).$promise;
|
}).$promise;
|
||||||
}
|
}
|
||||||
@@ -92,6 +93,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.close = function () {
|
$scope.close = function () {
|
||||||
$uibModalInstance.close();
|
$uibModalInstance.close($scope.enabled);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
$scope.disableLoading = false;
|
$scope.disableLoading = false;
|
||||||
$analytics.eventTrack('Disabled Two-step YubiKey');
|
$analytics.eventTrack('Disabled Two-step YubiKey');
|
||||||
toastr.success('YubiKey has been disabled.');
|
toastr.success('YubiKey has been disabled.');
|
||||||
|
$scope.enabled = response.Enabled;
|
||||||
$scope.close();
|
$scope.close();
|
||||||
}, function (response) {
|
}, function (response) {
|
||||||
toastr.error('Failed to disable.');
|
toastr.error('Failed to disable.');
|
||||||
@@ -102,6 +103,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$scope.close = function () {
|
$scope.close = function () {
|
||||||
$uibModalInstance.close();
|
$uibModalInstance.close($scope.enabled);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
76
src/app/settings/views/settingsTwoStepDuo.html
Normal file
76
src/app/settings/views/settingsTwoStepDuo.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" ng-click="close()" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||||
|
<h4 class="modal-title">
|
||||||
|
<i class="fa fa-key"></i> Two-step Login <small>duo</small>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<form name="authTwoStepForm" ng-submit="authTwoStepForm.$valid && auth(authModel)" api-form="authPromise"
|
||||||
|
ng-if="!authed">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Enter your master password to modify two-step login settings.</p>
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="authTwoStepForm.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in authTwoStepForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="masterPassword">Master Password</label>
|
||||||
|
<input type="password" id="masterPassword" name="MasterPasswordHash" ng-model="authModel.masterPassword"
|
||||||
|
class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="authTwoStepForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="authTwoStepForm.$loading"></i>Continue
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<form name="submitTwoStepForm" ng-submit="submitTwoStepForm.$valid && submit(updateModel)" api-form="submitPromise"
|
||||||
|
ng-if="authed">
|
||||||
|
<div class="modal-body">
|
||||||
|
<div ng-if="enabled">
|
||||||
|
<div class="callout callout-success">
|
||||||
|
<h4><i class="fa fa-check-circle"></i> Enabled</h4>
|
||||||
|
<p>Two-step log via Duo is enabled on your account.</p>
|
||||||
|
</div>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li><strong>Integration Key:</strong> {{updateModel.ikey}}</li>
|
||||||
|
<li><strong>Secret Key:</strong> ************</li>
|
||||||
|
<li><strong>API Hostname:</strong> {{updateModel.host}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div ng-if="!enabled">
|
||||||
|
<div class="callout callout-danger validation-errors" ng-show="submitTwoStepForm.$errors">
|
||||||
|
<h4>Errors have occurred</h4>
|
||||||
|
<ul>
|
||||||
|
<li ng-repeat="e in submitTwoStepForm.$errors">{{e}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<p>Setting up two-step login with Duo is easy, just enter the Duo application information below:</p>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="ikey">Integration Key</label>
|
||||||
|
<input type="text" id="ikey" name="IntegrationKey" ng-model="updateModel.ikey" class="form-control"
|
||||||
|
required api-field />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="skey">Secret Key</label>
|
||||||
|
<input type="password" id="skey" name="SecretKey" ng-model="updateModel.skey" class="form-control"
|
||||||
|
required api-field />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" show-errors>
|
||||||
|
<label for="host">API Hostname</label>
|
||||||
|
<input type="text" id="host" name="Host" placeholder="ex. api-xxxxxxxx.duosecurity.com"
|
||||||
|
ng-model="updateModel.host" class="form-control" required api-field />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="submit" class="btn btn-primary btn-flat" ng-disabled="submitTwoStepForm.$loading">
|
||||||
|
<i class="fa fa-refresh fa-spin loading-icon" ng-show="submitTwoStepForm.$loading"></i>
|
||||||
|
{{enabled ? 'Disable' : 'Enable'}}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-default btn-flat" ng-click="close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
6
src/images/loading.svg
Normal file
6
src/images/loading.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 100% 100%">
|
||||||
|
<text fill="%23333333" x="50%" y="50%" font-family="\'Open Sans\', \'Helvetica Neue\', Helvetica, Arial, sans-serif"
|
||||||
|
font-size="18" text-anchor="middle">
|
||||||
|
Loading...
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
@@ -29,10 +29,12 @@
|
|||||||
https://fonts.gstatic.com;
|
https://fonts.gstatic.com;
|
||||||
child-src
|
child-src
|
||||||
'self'
|
'self'
|
||||||
https://js.stripe.com;
|
https://js.stripe.com
|
||||||
|
https://*.duosecurity.com;
|
||||||
frame-src
|
frame-src
|
||||||
'self'
|
'self'
|
||||||
https://js.stripe.com;
|
https://js.stripe.com
|
||||||
|
https://*.duosecurity.com;
|
||||||
connect-src
|
connect-src
|
||||||
*;">
|
*;">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
@@ -85,6 +87,7 @@
|
|||||||
<script src="lib/forge/forge.js"></script>
|
<script src="lib/forge/forge.js"></script>
|
||||||
<script src="lib/papaparse/papaparse.js"></script>
|
<script src="lib/papaparse/papaparse.js"></script>
|
||||||
<script src="lib/clipboard/clipboard.js"></script>
|
<script src="lib/clipboard/clipboard.js"></script>
|
||||||
|
<script src="lib/duo/index.js"></script>
|
||||||
|
|
||||||
<script src="lib/angular/angular.js"></script>
|
<script src="lib/angular/angular.js"></script>
|
||||||
<script src="lib/angular-cookies/angular-cookies.js"></script>
|
<script src="lib/angular-cookies/angular-cookies.js"></script>
|
||||||
@@ -190,6 +193,7 @@
|
|||||||
<script src="app/settings/settingsChangePasswordController.js"></script>
|
<script src="app/settings/settingsChangePasswordController.js"></script>
|
||||||
<script src="app/settings/settingsChangeEmailController.js"></script>
|
<script src="app/settings/settingsChangeEmailController.js"></script>
|
||||||
<script src="app/settings/settingsTwoStepAuthenticatorController.js"></script>
|
<script src="app/settings/settingsTwoStepAuthenticatorController.js"></script>
|
||||||
|
<script src="app/settings/settingsTwoStepDuoController.js"></script>
|
||||||
<script src="app/settings/settingsTwoStepEmailController.js"></script>
|
<script src="app/settings/settingsTwoStepEmailController.js"></script>
|
||||||
<script src="app/settings/settingsTwoStepYubiController.js"></script>
|
<script src="app/settings/settingsTwoStepYubiController.js"></script>
|
||||||
<script src="app/settings/settingsSessionsController.js"></script>
|
<script src="app/settings/settingsSessionsController.js"></script>
|
||||||
|
|||||||
@@ -560,3 +560,24 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
background-position: -73px -88px;
|
background-position: -73px -88px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#duoFrameWrapper {
|
||||||
|
margin: 0 -36px 10px -36px;
|
||||||
|
background: ~"url('../images/loading.svg') 0 0 no-repeat";
|
||||||
|
|
||||||
|
@media (min-width: 360px) {
|
||||||
|
margin: 0 -10px 10px -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: @screen-sm) {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 304px;
|
||||||
|
max-width: 620px;
|
||||||
|
height: 500px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user