mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-11-08 10:13:40 +00:00
AngularJS (EOL since 2022-01) and AngularJS Material no longer receive
security fixes, so any new vulnerability in them will stay unpatched.
In AngularJS, templates are code: any attacker-controlled text that
ends up in a compiled template is evaluated as an expression, and
since 1.6 there is no expression sandbox, so a single
{{constructor.constructor('...')()}} gives full script execution.
SOGo renders untrusted data (mail bodies, invitations, attachment
names, contact fields) into such templates and currently relies on
server-side filtering, like stripping "{{" and "}}", to prevent this.
Any gap in that filtering, or a bypass of $sanitize/ng-bind-html,
would mean XSS in the webmail session: reading and sending mail as
the user, and access to calendars and contacts.
A Content Security Policy adds a second layer in the browser that
doesn't depend on catching every injection. The new
SOGoContentSecurityPolicy system default ("report-only" or "enforce",
disabled when unset) sends a strict policy with the pages of the web
interface:
script-src 'self' 'nonce-<random>' (no 'unsafe-inline'/'unsafe-eval')
style-src 'self' 'unsafe-inline' (Angular Material, CKEditor 5)
object-src 'none'; base-uri 'self'; frame-ancestors 'self'; ...
The policy is generated by sogod, since a fresh 128-bit nonce is needed
per response: it is shared by all components of the response and set
on the inline configuration scripts of the page frame and module views.
The page also gets ng-csp="no-unsafe-eval", so AngularJS evaluates
expressions with its interpreter instead of Function().
With the policy enforced, an expression injected into a compiled
template, such as {{constructor.constructor('...')()}}, can no longer
reach Function() and fails with an EvalError, and injected inline
scripts are blocked. Injected expressions are limited to calling
functions available on the scope.
Tested with Chromium in both modes on login, Mail (HTML messages,
remote content, invitations, compose), Calendar and Contacts editors,
Preferences and Administration: no violations from SOGo's own code.
With the default unset, the generated output is unchanged.
form-action is deliberately not restricted, so SAML2/CAS/OpenID
logins posting to an external IdP keep working. Scripts listed in
SOGoUIAdditionalJSFiles are served from WebServerResources and allowed,
but must not load scripts from other origins, use eval() or rely on
inline handlers; their dependencies have to be installed and listed
alongside them.
513 lines
28 KiB
XML
513 lines
28 KiB
XML
<?xml version="1.0" standalone="yes"?>
|
|
<!DOCTYPE var:component>
|
|
<var:component
|
|
className="UIxPageFrame"
|
|
xmlns="http://www.w3.org/1999/xhtml"
|
|
xmlns:var="http://www.skyrix.com/od/binding"
|
|
xmlns:const="http://www.skyrix.com/od/constant"
|
|
xmlns:rsrc="OGo:url"
|
|
xmlns:label="OGo:label"
|
|
const:jsFiles="Main.js, Common.js"
|
|
>
|
|
<script type="text/javascript" var:nonce="cspNonce">
|
|
var cookieUsername = <var:string var:value="cookieUsername.doubleQuotedString" const:escapeHTML="NO"/>;
|
|
var language = '<var:string var:value="language" const:escapeHTML="NO"/>';
|
|
var loginHint = '<var:string var:value="getLoginHint" const:escapeHTML="NO"/>'
|
|
</script>
|
|
|
|
<!--
|
|
MAIN CONTENT ROW
|
|
Content of the application view injected injected in the element bellow
|
|
MUST be the first html element after body
|
|
SHOULD be a main tag (with role="main")
|
|
-->
|
|
<main class="view md-default-theme md-background md-hue-1 md-bg"
|
|
layout-gt-md="row" layout-align-gt-md="center start" layout-fill="layout-fill"
|
|
ui-view="login"
|
|
ng-controller="LoginController as app">
|
|
<md-content class="ng-cloak" flex="100"
|
|
layout="column" layout-align="start stretch"
|
|
ng-show="app.showLogin">
|
|
<var:if condition="hasMotd">
|
|
<div flex="100" class="motd hide show-gt-md md-default-theme md-bdr md-accent">
|
|
<var:string var:value="motd" const:escapeHTML="NO" />
|
|
</div>
|
|
</var:if>
|
|
<div id="loginContent" layout="column" layout-gt-md="row" flex="100" class="md-whiteframe-3dp">
|
|
<div class="sg-logo" flex-gt-md="50">
|
|
<div layout="row" class="md-padding">
|
|
<div class="md-flex hide show-gt-md"><!-- push logo to the right on larger screens --></div>
|
|
<img const:alt="*" class="md-margin" rsrc:src="img/sogo-full.svg"/>
|
|
</div>
|
|
<var:if condition="hasMotd">
|
|
<div layout="row">
|
|
<div class="motd hide-gt-md"><var:string var:value="motdEscaped" const:escapeHTML="NO" /></div>
|
|
</div>
|
|
</var:if>
|
|
</div>
|
|
|
|
|
|
<div class="sg-login md-default-theme md-bg md-accent" flex-gt-md="50">
|
|
<var:if condition="doFullLogin">
|
|
<div id="login" class="sg-login-content md-padding">
|
|
<form name="loginForm" layout="column"
|
|
ng-cloak="ng-cloak"
|
|
ng-submit="app.login()">
|
|
<var:if condition="hasLoginSuffix">
|
|
<input type="hidden" ng-model="app.creds.loginSuffix" var:value="loginSuffix"/>
|
|
</var:if>
|
|
<div ng-if="!app.loginState">
|
|
|
|
<md-input-container class="md-block">
|
|
<label><var:string label:value="Username"/></label>
|
|
<md-icon>person</md-icon>
|
|
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.creds.username" ng-required="true" ng-change="app.usernameChanged()" ng-blur="app.retrievePasswordRecoveryEnabled()" />
|
|
</md-input-container>
|
|
|
|
<md-input-container class="md-block">
|
|
<label><var:string label:value="Password"/></label>
|
|
<md-icon>vpn_key</md-icon>
|
|
<input id="passwordField" type="password" ng-model="app.creds.password" ng-required="true"/>
|
|
<md-icon id="password-visibility-icon" ng-click="app.changePasswordVisibility()">visibility</md-icon>
|
|
</md-input-container>
|
|
|
|
|
|
<!-- LANGUAGES SELECT -->
|
|
<div layout="row" layout-align="start end">
|
|
<md-icon>language</md-icon>
|
|
<md-input-container class="md-flex">
|
|
<label><var:string label:value="choose"/></label>
|
|
<md-select ng-model="app.creds.language"
|
|
var:placeholder="localizedLanguage"
|
|
ng-change="app.changeLanguage($event)">
|
|
<var:foreach list="languages" item="item">
|
|
<md-option var:value="item">
|
|
<var:string value="languageText"/>
|
|
</md-option>
|
|
</var:foreach>
|
|
</md-select>
|
|
</md-input-container>
|
|
</div>
|
|
|
|
<!-- DOMAINS SELECT -->
|
|
<var:if condition="hasLoginDomains">
|
|
<div layout="row" layout-align="start end">
|
|
<md-icon>domain</md-icon>
|
|
<md-input-container class="md-flex">
|
|
<md-select class="md-flex" ng-model="app.creds.domain" label:placeholder="choose" ng-change="app.retrievePasswordRecoveryEnabled()">
|
|
<var:foreach list="loginDomains" item="item">
|
|
<md-option var:value="item">
|
|
<var:string value="item"/>
|
|
</md-option>
|
|
</var:foreach>
|
|
</md-select>
|
|
</md-input-container>
|
|
</div>
|
|
</var:if>
|
|
|
|
<div layout="row" layout-align="center center">
|
|
<md-switch class="md-accent md-hue-2"
|
|
ng-model="app.creds.rememberLogin"
|
|
label:arial-label="Remember username">
|
|
<var:string label:value="Remember username"/>
|
|
</md-switch>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Password recovery -->
|
|
<div layout="row" layout-align="center center" ng-if="app.passwordRecovery.passwordRecoveryEnabled">
|
|
<div ng-if="app.showLogin">
|
|
<a href="#" ng-click="app.passwordRecoveryInfo()" sg-ripple-click="loginContent" class="password-lost-link"><var:string label:value="Password lost"/></a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CONNECT BUTTON -->
|
|
<div layout="row" layout-align="space-between center" ng-if="!app.loginState">
|
|
<md-button class="md-icon-button"
|
|
label:aria-label="About"
|
|
ng-click="app.showAbout()">
|
|
<md-icon>info</md-icon>
|
|
</md-button>
|
|
<div>
|
|
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
|
label:aria-label="Connect"
|
|
ng-if="!app.loginState"
|
|
ng-disabled="loginForm.$invalid"
|
|
sg-ripple-click="loginContent">
|
|
<md-icon>arrow_forward</md-icon>
|
|
</md-button>
|
|
</div>
|
|
</div>
|
|
|
|
<sg-ripple class="md-default-theme md-accent md-bg"
|
|
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
|
|
<sg-ripple-content class="md-flex ng-hide"
|
|
layout="column" layout-align="center center" layout-fill="layout-fill"
|
|
ng-switch="app.loginState">
|
|
|
|
<!-- Authenticating -->
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="authenticating">
|
|
<md-progress-circular class="md-hue-1"
|
|
md-mode="indeterminate"
|
|
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
|
<var:string label:value="Authenticating"/>
|
|
</div>
|
|
</div>
|
|
|
|
<var:if condition="isTotpEnabled">
|
|
<!-- TOTP Code -->
|
|
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
|
ng-switch-when="totpcode">
|
|
<div flex="80" flex-sm="50" flex-gt-sm="40">
|
|
<md-input-container class="md-block">
|
|
<label><var:string label:value="Verification Code"/></label>
|
|
<md-icon>lock</md-icon>
|
|
<input type="text" inputmode="numeric" autocomplete="off"
|
|
ng-pattern="app.verificationCodePattern"
|
|
ng-model="app.creds.verificationCode"
|
|
ng-required="app.loginState == 'totpcode'"
|
|
sg-focus-on="totpcode"/>
|
|
<div class="sg-hint"><var:string label:value="Enter the 6-digit verification code from your TOTP application."/></div>
|
|
</md-input-container>
|
|
<div layout="row" layout-align="space-between center">
|
|
<md-button class="md-icon-button"
|
|
label:aria-label="Cancel"
|
|
ng-click="app.restoreLogin()"
|
|
sg-ripple-click="loginContent">
|
|
<md-icon>arrow_backward</md-icon>
|
|
</md-button>
|
|
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
|
label:aria-label="Connect"
|
|
ng-if="app.loginState == 'totpcode'"
|
|
ng-disabled="loginForm.$invalid"
|
|
ng-click="app.login()">
|
|
<md-icon>arrow_forward</md-icon>
|
|
</md-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- TOTP has been disabled -->
|
|
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
|
ng-switch-when="totpdisabled">
|
|
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
|
<var:string label:value="Welcome"/> {{app.cn}}
|
|
</div>
|
|
<div class="md-padding" layout="row" layout-align="start center">
|
|
<md-icon>priority_high</md-icon>
|
|
<div class="md-padding">
|
|
<var:string label:value="Two-factor authentication has been disabled. Visit the Preferences module to restore two-factor authentication and reconfigure your TOTP application."/>
|
|
</div>
|
|
</div>
|
|
<div layout="row" layout-align="end center">
|
|
<md-button
|
|
ng-click="app.continueLogin()"
|
|
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</var:if>
|
|
|
|
<!-- Password policy: Password is expired / password recovery-->
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="passwordchange">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="!(app.isInPasswordRecoveryMode() || app.isPasswordExpiredSecurity())">watch_later</md-icon>
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="app.isInPasswordRecoveryMode() || app.isPasswordExpiredSecurity()">vpn_key</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="!app.isInPasswordRecoveryMode()">
|
|
<span ng-if="!app.isPasswordExpiredSecurity()"><var:string label:value="Your password has expired, please enter a new one below"/></span>
|
|
<span ng-if="app.isPasswordExpiredSecurity()"><var:string label:value="Your password is too weak. Please choose a stronger password to enhance your security"/></span>
|
|
</div>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.isInPasswordRecoveryMode()">
|
|
<var:string label:value="Please enter a new password below"/>
|
|
</div>
|
|
<div flex="100">
|
|
<div layout="row" layout-xs="column">
|
|
<md-input-container class="md-block" flex="flex" ng-if="!(app.isInPasswordRecoveryMode())">
|
|
<label class="input-login-label"><var:string label:value="Current password"/>
|
|
</label>
|
|
<input class="md-input input-login-input" type="password" sg-no-dirty-check="true" ng-model="app.passwords.oldPassword"/>
|
|
</md-input-container>
|
|
<md-input-container class="md-block" flex="flex">
|
|
<label class="input-login-label"><var:string label:value="New password"/>
|
|
</label>
|
|
<input class="input-login-input" type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPassword"/>
|
|
<div class="sg-hint">
|
|
<ul class="sg-padded">
|
|
<li ng-repeat="item in app.passwordPolicy">
|
|
{{ item.label }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</md-input-container>
|
|
<md-input-container class="md-block" flex="flex">
|
|
<label class="input-login-label"><var:string label:value="Confirmation"/>
|
|
</label>
|
|
<input class="input-login-input" type="password" name="newPasswordConfirmation" sg-no-dirty-check="true" ng-model="app.passwords.newPasswordConfirmation"/>
|
|
<div ng-messages="loginForm.newPasswordConfirmation.$error">
|
|
<div class="input-login-mismtach" ng-message="newPasswordMismatch"><var:string label:value="Passwords don't match"/></div>
|
|
</div>
|
|
</md-input-container>
|
|
</div>
|
|
<div layout="row" layout-align="end center">
|
|
<md-button ng-click="app.changePassword()" type="button" ng-disabled="!app.canChangePassword(loginForm)">
|
|
<var:string label:value="Change"/>
|
|
</md-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Password policy: Grace period -->
|
|
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
|
ng-switch-when="passwordwillexpire">
|
|
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
|
<var:string label:value="Welcome"/> {{app.cn}}
|
|
</div>
|
|
<div class="md-padding" layout="row" layout-align="start center">
|
|
<md-icon>priority_high</md-icon>
|
|
<div class="md-padding">{{app.errorMessage}}</div>
|
|
</div>
|
|
<div layout="row" layout-align="end center">
|
|
<md-button
|
|
ng-click="app.loginState = 'passwordexpired'"><var:string label:value="Change your Password"/></md-button>
|
|
<md-button
|
|
ng-click="app.continueLogin()"
|
|
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Password recovery -->
|
|
<var:if condition="hasPasswordRecovery">
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="passwordrecovery">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">vpn_key</md-icon>
|
|
<div flex="100">
|
|
<div layout="row" layout-xs="column" class="md-padding" layout-align="center center">
|
|
<div ng-if="app.passwordRecovery.showLoader">
|
|
<md-progress-circular class="md-hue-1"
|
|
md-mode="indeterminate"
|
|
md-diameter="32"><!-- password recovery progress --></md-progress-circular>
|
|
</div>
|
|
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
|
<div ng-if="!app.passwordRecovery.showLoader">
|
|
{{ app.passwordRecovery.passwordRecoveryQuestion }}
|
|
<md-input-container class="md-block">
|
|
<label><var:string label:value="Answer"/></label>
|
|
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.passwordRecovery.passwordRecoveryQuestionAnswer" />
|
|
</md-input-container>
|
|
</div>
|
|
</div>
|
|
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
|
<div ng-if="!app.passwordRecovery.showLoader">
|
|
{{ app.passwordRecovery.passwordRecoverySecondaryEmailText }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div layout="row" layout-align="end center" ng-if="!app.passwordRecovery.showLoader">
|
|
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
|
<var:string label:value="Back"/>
|
|
</md-button>
|
|
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
|
<md-button ng-click="app.passwordRecoveryCheck()" type="button" >
|
|
<var:string label:value="Next"/>
|
|
</md-button>
|
|
</div>
|
|
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
|
<md-button ng-click="app.passwordRecoveryEmail()" type="button" >
|
|
<var:string label:value="Next"/>
|
|
</md-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="sendrecoverymail">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">local_shipping</md-icon>
|
|
<div flex="100">
|
|
<div layout="row" layout-xs="column" class="md-padding">
|
|
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
|
<var:string label:value="A password reset link has been sent, please check your recovery e-mail mailbox and click on the link"/>
|
|
</div>
|
|
</div>
|
|
<div layout="row" layout-align="end center">
|
|
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
|
<var:string label:value="Back"/>
|
|
</md-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</var:if>
|
|
|
|
<!-- Logged in -->
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="logged">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
|
<var:string label:value="Welcome"/> {{app.cn}}
|
|
</div>
|
|
</div>
|
|
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="message">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
|
{{app.errorMessage}}
|
|
</div>
|
|
<md-button
|
|
ng-click="app.continueLogin()"
|
|
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
|
</div>
|
|
|
|
<!-- Error -->
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="error">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
|
{{app.errorMessage}}
|
|
</div>
|
|
<md-button
|
|
ng-click="app.restoreLogin()"
|
|
sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
|
|
</div>
|
|
|
|
</sg-ripple-content>
|
|
</form>
|
|
|
|
</div>
|
|
</var:if>
|
|
<var:if condition="doPartialLogin">
|
|
<div id="login" class="sg-login-content md-padding">
|
|
<form name="loginForm" layout="column"
|
|
ng-cloak="ng-cloak"
|
|
ng-submit="app.loginName()">
|
|
<div ng-if="!app.loginState">
|
|
|
|
<md-input-container class="md-block">
|
|
<label><var:string label:value="Username"/></label>
|
|
<md-icon>person</md-icon>
|
|
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.creds.username" ng-required="true" ng-change="app.usernameChanged()" ng-blur="app.retrievePasswordRecoveryEnabled()" />
|
|
</md-input-container>
|
|
|
|
<!-- LANGUAGES SELECT -->
|
|
<div layout="row" layout-align="start end">
|
|
<md-icon>language</md-icon>
|
|
<md-input-container class="md-flex">
|
|
<label><var:string label:value="choose"/></label>
|
|
<md-select ng-model="app.creds.language"
|
|
var:placeholder="localizedLanguage"
|
|
ng-change="app.changeLanguage($event)">
|
|
<var:foreach list="languages" item="item">
|
|
<md-option var:value="item">
|
|
<var:string value="languageText"/>
|
|
</md-option>
|
|
</var:foreach>
|
|
</md-select>
|
|
</md-input-container>
|
|
</div>
|
|
|
|
<var:if condition="hasUrlCreateAccount">
|
|
<div layout="row" layout-align="center center">
|
|
<a var:href="urlCreateAccount" target="_blank" class="create-account-link"><var:string label:value="Create an account"/></a>
|
|
</div>
|
|
</var:if>
|
|
</div>
|
|
|
|
<!-- CONNECT BUTTON -->
|
|
<div layout="row" layout-align="space-between center" ng-if="!app.loginState">
|
|
<md-button class="md-icon-button"
|
|
label:aria-label="About"
|
|
ng-click="app.showAbout()">
|
|
<md-icon>info</md-icon>
|
|
</md-button>
|
|
<div>
|
|
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
|
label:aria-label="Connect"
|
|
ng-if="!app.loginState"
|
|
ng-disabled="loginForm.$invalid"
|
|
sg-ripple-click="loginContent">
|
|
<md-icon>arrow_forward</md-icon>
|
|
</md-button>
|
|
</div>
|
|
</div>
|
|
|
|
<sg-ripple class="md-default-theme md-accent md-bg"
|
|
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
|
|
<sg-ripple-content class="md-flex ng-hide"
|
|
layout="column" layout-align="center center" layout-fill="layout-fill"
|
|
ng-switch="app.loginState">
|
|
|
|
<!-- Authenticating -->
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="authenticating">
|
|
<md-progress-circular class="md-hue-1"
|
|
md-mode="indeterminate"
|
|
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
|
<var:string label:value="Authenticating"/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logged in -->
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="logged">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
|
<var:string label:value="Welcome"/> {{app.cn}}
|
|
</div>
|
|
</div>
|
|
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="message">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
|
{{app.errorMessage}}
|
|
</div>
|
|
<md-button
|
|
ng-click="app.continueLogin()"
|
|
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
|
</div>
|
|
|
|
<!-- Error -->
|
|
<div layout="column" layout-align="center center"
|
|
ng-switch-when="error">
|
|
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
|
|
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
|
{{app.errorMessage}}
|
|
</div>
|
|
<md-button
|
|
ng-click="app.restoreLogin()"
|
|
sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
|
|
</div>
|
|
|
|
</sg-ripple-content>
|
|
</form>
|
|
</div>
|
|
</var:if>
|
|
</div>
|
|
</div>
|
|
</md-content>
|
|
|
|
</main>
|
|
|
|
<script type="text/ng-template" id="aboutBox.html">
|
|
<md-dialog flex="50" flex-xs="100">
|
|
<md-dialog-content class="md-dialog-content">
|
|
<p><a href="http://sogo.nu/" target="_new">sogo.nu</a></p>
|
|
<p>Version <var:string value="version"/> (<var:string value="buildDate" />)</p>
|
|
<br/>
|
|
<p><var:string label:value="AboutBox" const:escapeHTML="NO"/></p>
|
|
<!--<img class="full-image" const:alt="Inverse" rsrc:src="img/inverse.png"/>-->
|
|
</md-dialog-content>
|
|
<md-dialog-actions>
|
|
<md-button ng-click="about.closeDialog()"><var:string label:value="Close"/></md-button>
|
|
</md-dialog-actions>
|
|
</md-dialog>
|
|
</script>
|
|
|
|
</var:component>
|