From f52a4ba788ee16fb5805a08b2866038db67c3331 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 27 Aug 2015 15:06:31 +0200 Subject: [PATCH] External apps authentication --- CHANGELOG.md | 1 + app/coffee/app.coffee | 48 ++++- app/coffee/modules/auth.coffee | 4 +- app/coffee/modules/resources.coffee | 4 + app/locales/locale-en.json | 8 + .../external-app.controller.coffee | 58 +++++++ .../external-app.controller.spec.coffee | 164 ++++++++++++++++++ app/modules/external-apps/external-app.jade | 28 +++ app/modules/external-apps/external-app.scss | 85 +++++++++ .../external-apps/external-app.service.coffee | 14 ++ .../external-app.service.spec.coffee | 44 +++++ .../external-apps/external-apps.module.coffee | 1 + .../navigation-bar.directive.coffee | 5 +- .../navigation-bar/navigation-bar.jade | 2 +- .../navigation-bar.service.coffee | 15 ++ .../external-apps-resource.service.coffee | 27 +++ app/modules/resources/resources.coffee | 3 +- app/modules/services/app-meta.service.coffee | 8 + app/partials/auth/login.jade | 4 +- app/styles/components/beta.scss | 2 +- app/styles/dependencies/responsive.scss | 3 +- app/svg/logo-color.svg | 12 ++ app/svg/logo.svg | 4 +- 23 files changed, 524 insertions(+), 20 deletions(-) create mode 100644 app/modules/external-apps/external-app.controller.coffee create mode 100644 app/modules/external-apps/external-app.controller.spec.coffee create mode 100644 app/modules/external-apps/external-app.jade create mode 100644 app/modules/external-apps/external-app.scss create mode 100644 app/modules/external-apps/external-app.service.coffee create mode 100644 app/modules/external-apps/external-app.service.spec.coffee create mode 100644 app/modules/external-apps/external-apps.module.coffee create mode 100644 app/modules/navigation-bar/navigation-bar.service.coffee create mode 100644 app/modules/resources/external-apps-resource.service.coffee create mode 100644 app/svg/logo-color.svg diff --git a/CHANGELOG.md b/CHANGELOG.md index d310bc17..c9abc248 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Ability to choose a theme (thanks to [@astagi](https://github.com/astagi)) - Inline viewing of image attachments (thanks to [@brettp](https://github.com/brettp)). - Autocomplete for usernames, user stories, tasks, issues, and wiki pages in text areas (thanks to [@brettp](https://github.com/brettp)). +- Support authentication via Application Tokens - i18n. - Add polish (pl) translation. - Add portuguese (Brazil) (pt_BR) translation. diff --git a/app/coffee/app.coffee b/app/coffee/app.coffee index bc5b7ca4..acfe69ab 100644 --- a/app/coffee/app.coffee +++ b/app/coffee/app.coffee @@ -68,7 +68,6 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven }, title: "HOME.PAGE_TITLE", description: "HOME.PAGE_DESCRIPTION", - loader: true } ) @@ -334,22 +333,25 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $routeProvider.when("/login", { templateUrl: "auth/login.html", - title: "LOGIN.PAGE_TITLE" - description: "LOGIN.PAGE_DESCRIPTION" + title: "LOGIN.PAGE_TITLE", + description: "LOGIN.PAGE_DESCRIPTION", + disableHeader: true } ) $routeProvider.when("/register", { templateUrl: "auth/register.html", title: "REGISTER.PAGE_TITLE", - description: "REGISTER.PAGE_DESCRIPTION" + description: "REGISTER.PAGE_DESCRIPTION", + disableHeader: true } ) $routeProvider.when("/forgot-password", { templateUrl: "auth/forgot-password.html", title: "FORGOT_PASSWORD.PAGE_TITLE", - description: "FORGOT_PASSWORD.PAGE_DESCRIPTION" + description: "FORGOT_PASSWORD.PAGE_DESCRIPTION", + disableHeader: true } ) $routeProvider.when("/change-password", @@ -357,6 +359,7 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven templateUrl: "auth/change-password-from-recovery.html", title: "CHANGE_PASSWORD.PAGE_TITLE", description: "CHANGE_PASSWORD.PAGE_TITLE", + disableHeader: true } ) $routeProvider.when("/change-password/:token", @@ -364,13 +367,29 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven templateUrl: "auth/change-password-from-recovery.html", title: "CHANGE_PASSWORD.PAGE_TITLE", description: "CHANGE_PASSWORD.PAGE_TITLE", + disableHeader: true } ) $routeProvider.when("/invitation/:token", { templateUrl: "auth/invitation.html", title: "INVITATION.PAGE_TITLE", - description: "INVITATION.PAGE_DESCRIPTION" + description: "INVITATION.PAGE_DESCRIPTION", + disableHeader: true, + access: { + requiresLogin: true + } + } + ) + $routeProvider.when("/external-apps", + { + templateUrl: "external-apps/external-app.html", + title: "EXTERNAL_APP.PAGE_TITLE", + description: "EXTERNAL_APP.PAGE_DESCRIPTION", + controller: "ExternalApp", + controllerAs: "vm", + disableHeader: true, + mobileViewport: true } ) @@ -411,8 +430,8 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven $location.path($navUrls.resolve("error")) $location.replace() else if response.status == 401 - nextPath = $location.path() - $location.url($navUrls.resolve("login")).search("next=#{nextPath}") + nextUrl = encodeURIComponent($location.url()) + $location.url($navUrls.resolve("login")).search("next=#{nextUrl}") return $q.reject(response) @@ -537,7 +556,7 @@ i18nInit = (lang, $translate) -> checksley.updateMessages('default', messages) -init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService) -> +init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $navUrls, appMetaService, projectService, loaderService, navigationBarService) -> $log.debug("Initialize application") # Taiga Plugins @@ -590,6 +609,15 @@ init = ($log, $rootscope, $auth, $events, $analytics, $translate, $location, $na description = $translate.instant(next.description or "") appMetaService.setAll(title, description) + if next.mobileViewport + appMetaService.addMobileViewport() + else + appMetaService.removeMobileViewport() + + if next.disableHeader + navigationBarService.disableHeader() + else + navigationBarService.enableHeader() modules = [ # Main Global Modules @@ -625,6 +653,7 @@ modules = [ "taigaProfile", "taigaHome", "taigaUserTimeline", + "taigaExternalApps", # template cache "templates", @@ -664,5 +693,6 @@ module.run([ "tgAppMetaService", "tgProjectService", "tgLoader", + "tgNavigationBarService" init ]) diff --git a/app/coffee/modules/auth.coffee b/app/coffee/modules/auth.coffee index ddab6374..88a601a0 100644 --- a/app/coffee/modules/auth.coffee +++ b/app/coffee/modules/auth.coffee @@ -221,12 +221,12 @@ LoginDirective = ($auth, $confirm, $location, $config, $routeParams, $navUrls, $ link = ($scope, $el, $attrs) -> onSuccess = (response) -> if $routeParams['next'] and $routeParams['next'] != $navUrls.resolve("login") - nextUrl = $routeParams['next'] + nextUrl = decodeURIComponent($routeParams['next']) else nextUrl = $navUrls.resolve("home") $events.setupConnection() - $location.path(nextUrl) + $location.url(nextUrl) onError = (response) -> $confirm.notify("light-error", $translate.instant("LOGIN_FORM.ERROR_AUTH_INCORRECT")) diff --git a/app/coffee/modules/resources.coffee b/app/coffee/modules/resources.coffee index 0bfd7199..f17841e1 100644 --- a/app/coffee/modules/resources.coffee +++ b/app/coffee/modules/resources.coffee @@ -149,6 +149,10 @@ urls = { # locales "locales": "/locales" + + # Application tokens + "applications": "/applications" + "application-tokens": "/application-tokens" } # Initialize api urls service diff --git a/app/locales/locale-en.json b/app/locales/locale-en.json index f465eb4c..42ab4fbc 100644 --- a/app/locales/locale-en.json +++ b/app/locales/locale-en.json @@ -1268,5 +1268,13 @@ "BLOCKED": "{{username}} has blocked {{obj_name}}", "UNBLOCKED": "{{username}} has unblocked {{obj_name}}", "NEW_USER": "{{username}} has joined Taiga" + }, + "EXTERNAL_APP": { + "PAGE_TITLE": "An external app requires authentication", + "PAGE_DESCRIPTION": "An external app requires authentication", + "AUTHORIZATION_REQUEST": "Authorize {{application}} to use your Taiga account?", + "LOGIN_WITH_ANOTHER_USER": "Login with another user", + "AUTHORIZE_APP": "Authorize app", + "CANCEL": "Cancel" } } diff --git a/app/modules/external-apps/external-app.controller.coffee b/app/modules/external-apps/external-app.controller.coffee new file mode 100644 index 00000000..faec88ca --- /dev/null +++ b/app/modules/external-apps/external-app.controller.coffee @@ -0,0 +1,58 @@ +taiga = @.taiga + +class ExternalAppController extends taiga.Controller + @.$inject = [ + "$routeParams", + "tgExternalAppsService", + "$window", + "tgCurrentUserService", + "$location", + "$tgNavUrls", + "tgXhrErrorService", + "tgLoader" + ] + + constructor: (@routeParams, @externalAppsService, @window, @currentUserService, + @location, @navUrls, @xhrError, @loader) -> + + @loader.start(false) + @._applicationId = @routeParams.application + @._state = @routeParams.state + @._getApplicationToken() + @._user = @currentUserService.getUser() + @._application = null + nextUrl = encodeURIComponent(@location.url()) + loginUrl = @navUrls.resolve("login") + @.loginWithAnotherUserUrl = "#{loginUrl}?next=#{nextUrl}" + + taiga.defineImmutableProperty @, "user", () => @._user + taiga.defineImmutableProperty @, "application", () => @._application + + _redirect: (applicationToken) => + nextUrl = applicationToken.get("next_url") + @window.open(nextUrl, "_self") + + _getApplicationToken: => + return @externalAppsService.getApplicationToken(@._applicationId, @._state) + .then (data) => + @._application = data.get("application") + if data.get("auth_code") + @._redirect(data) + else + @loader.pageLoaded() + + .catch (xhr) => + return @xhrError.response(xhr) + + cancel: () -> + @window.history.back() + + createApplicationToken: => + return @externalAppsService.authorizeApplicationToken(@._applicationId, @._state) + .then (data) => + @._redirect(data) + .catch (xhr) => + return @xhrError.response(xhr) + + +angular.module("taigaExternalApps").controller("ExternalApp", ExternalAppController) diff --git a/app/modules/external-apps/external-app.controller.spec.coffee b/app/modules/external-apps/external-app.controller.spec.coffee new file mode 100644 index 00000000..d2ea1a9b --- /dev/null +++ b/app/modules/external-apps/external-app.controller.spec.coffee @@ -0,0 +1,164 @@ +describe "ExternalAppController", -> + provide = null + $controller = null + $rootScope = null + mocks = {} + + _inject = (callback) -> + inject (_$controller_, _$rootScope_) -> + $rootScope = _$rootScope_ + $controller = _$controller_ + + _mockRouteParams = () -> + mocks.routeParams = {} + provide.value "$routeParams", mocks.routeParams + + _mockTgExternalAppsService = () -> + mocks.tgExternalAppsService = { + getApplicationToken: sinon.stub() + authorizeApplicationToken: sinon.stub() + } + provide.value "tgExternalAppsService", mocks.tgExternalAppsService + + _mockWindow = () -> + mocks.window = { + open: sinon.stub() + history: { + back: sinon.stub() + } + } + provide.value "$window", mocks.window + + _mockTgCurrentUserService = () -> + mocks.tgCurrentUserService = { + getUser: sinon.stub() + } + provide.value "tgCurrentUserService", mocks.tgCurrentUserService + + _mockLocation = () -> + mocks.location = { + url: sinon.stub() + } + provide.value "$location", mocks.location + + _mockTgNavUrls = () -> + mocks.tgNavUrls = { + resolve: sinon.stub() + } + provide.value "$tgNavUrls", mocks.tgNavUrls + + _mockTgXhrErrorService = () -> + mocks.tgXhrErrorService = { + response: sinon.spy(), + notFound: sinon.spy() + } + provide.value "tgXhrErrorService", mocks.tgXhrErrorService + + _mockTgLoader = () -> + mocks.tgLoader = { + start: sinon.stub(), + pageLoaded: sinon.stub() + } + provide.value "tgLoader", mocks.tgLoader + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockRouteParams() + _mockTgExternalAppsService() + _mockWindow() + _mockTgCurrentUserService() + _mockLocation() + _mockTgNavUrls() + _mockTgXhrErrorService() + _mockTgLoader() + return null + + beforeEach -> + module "taigaExternalApps" + _mocks() + _inject() + + it "not existing application", (done) -> + $scope = $rootScope.$new() + + mocks.routeParams.application = 6 + mocks.routeParams.state = "testing-state" + + xhr = { + status: 404 + } + + mocks.tgExternalAppsService.getApplicationToken.withArgs(mocks.routeParams.application, mocks.routeParams.state).promise().reject(xhr) + + ctrl = $controller("ExternalApp") + + setTimeout ( -> + expect(mocks.tgLoader.start.withArgs(false)).to.be.calledOnce + expect(mocks.tgXhrErrorService.response.withArgs(xhr)).to.be.calledOnce + done() + ) + + it "existing application and existing token, automatically redirecting to next url", (done) -> + $scope = $rootScope.$new() + + mocks.routeParams.application = 6 + mocks.routeParams.state = "testing-state" + + applicationToken = Immutable.fromJS({ + auth_code: "testing-auth-code" + next_url: "http://next.url" + }) + + mocks.tgExternalAppsService.getApplicationToken.withArgs(mocks.routeParams.application, mocks.routeParams.state).promise().resolve(applicationToken) + + ctrl = $controller("ExternalApp") + + setTimeout ( -> + expect(mocks.tgLoader.start.withArgs(false)).to.be.calledOnce + expect(mocks.window.open.callCount).to.be.equal(1) + expect(mocks.window.open.calledWith("http://next.url")).to.be.true + done() + ) + + it "existing application and creating new token", (done) -> + $scope = $rootScope.$new() + + mocks.routeParams.application = 6 + mocks.routeParams.state = "testing-state" + + applicationToken = Immutable.fromJS({}) + mocks.tgExternalAppsService.getApplicationToken.withArgs(mocks.routeParams.application, mocks.routeParams.state).promise().resolve(applicationToken) + + ctrl = $controller("ExternalApp") + + applicationToken = Immutable.fromJS({ + next_url: "http://next.url" + auth_code: "testing-auth-code" + }) + + mocks.tgExternalAppsService.authorizeApplicationToken.withArgs(mocks.routeParams.application, mocks.routeParams.state).promise().resolve(applicationToken) + + ctrl.createApplicationToken() + + setTimeout ( -> + expect(mocks.tgLoader.start.withArgs(false)).to.be.calledOnce + expect(mocks.tgLoader.pageLoaded).to.be.calledOnce + expect(mocks.window.open.callCount).to.be.equal(1) + expect(mocks.window.open.calledWith("http://next.url")).to.be.true + done() + ) + + it "cancel back to previous url", () -> + $scope = $rootScope.$new() + + mocks.routeParams.application = 6 + mocks.routeParams.state = "testing-state" + + applicationToken = Immutable.fromJS({}) + mocks.tgExternalAppsService.getApplicationToken.withArgs(mocks.routeParams.application, mocks.routeParams.state).promise().resolve(applicationToken) + + ctrl = $controller("ExternalApp") + expect(mocks.window.history.back.callCount).to.be.equal(0) + ctrl.cancel() + expect(mocks.window.history.back.callCount).to.be.equal(1) diff --git a/app/modules/external-apps/external-app.jade b/app/modules/external-apps/external-app.jade new file mode 100644 index 00000000..59d42918 --- /dev/null +++ b/app/modules/external-apps/external-app.jade @@ -0,0 +1,28 @@ +section.external-app-wrapper + div.logo + include ../../svg/logo-color.svg + + h1 Taiga + + h2(translate="EXTERNAL_APP.AUTHORIZATION_REQUEST", translate-values="{application: vm.application.get('name')}") + + div.user-card.avatar + .card-inner + div.user-image + img(ng-src="{{::vm.user.get('photo')}}", alt="{{::vm.user.get('full_name_display')}}") + div.user-data + h3 {{ ::vm.user.get("full_name_display") }} + p {{ ::vm.user.get("email") }} + a(ng-href="{{::vm.loginWithAnotherUserUrl}}", title="{{'EXTERNAL_APP.LOGIN_WITH_ANOTHER_USER' | translate}}", translate="EXTERNAL_APP.LOGIN_WITH_ANOTHER_USER") + + div.app-card + .card-inner + div.app-image + img(ng-src="{{::vm.application.get('icon_url')}}", alt="{{::vm.application.get('name')}}") + div.app-data + h3 {{ ::vm.application.get("name") }} + a(ng-href="{{::vm.application.get('web')}}", title="{{::vm.application.get('name')}}", target="_blank") {{ ::vm.application.get('web') }} + p {{ ::vm.application.get("description") }} + + a.button-green(href="#", ng-click="vm.createApplicationToken()", title="{{'EXTERNAL_APP.AUTHORIZE_APP' | translate}}", translate="EXTERNAL_APP.AUTHORIZE_APP") + a.cancel(href="#", ng-click="vm.cancel()", title="{{'EXTERNAL_APP.CANCEL' | translate}}", translate="EXTERNAL_APP.CANCEL") diff --git a/app/modules/external-apps/external-app.scss b/app/modules/external-apps/external-app.scss new file mode 100644 index 00000000..9a387368 --- /dev/null +++ b/app/modules/external-apps/external-app.scss @@ -0,0 +1,85 @@ +.external-app-wrapper { + margin: 2rem auto; + text-align: center; + width: 480px; + .logo { + height: 6rem; + margin: 0 auto; + width: 6rem; + } + h1 { + margin-bottom: 0; + } + .app-card, + .user-card { + line-height: 1.4; + margin-bottom: 2rem; + text-align: left; + .card-inner { + display: flex; + } + img { + width: 100%; + } + h3, + p { + margin: 0; + } + h3 { + @extend %large; + } + a { + @extend %xsmall; + display: block; + } + } + .app-card { + .app-image { + flex-basis: 100px; + margin-right: 1rem; + max-width: 105px; + } + .app-data { + flex: 1; + } + a { + margin-bottom: .5rem; + + } + p { + @extend %xsmall; + } + } + .user-card { + background: $card; + border: 1px solid $card-hover; + padding: 1rem; + .card-inner { + margin-bottom: .5rem; + } + .user-image { + flex-basis: 50px; + margin-right: 1rem; + max-width: 55px; + } + } + .button-green { + display: block; + } + .cancel { + @extend %small; + display: block; + margin-top: .5rem; + text-align: left; + } +} + +@include breakpoint(mobile) { + .external-app-wrapper { + margin: 0; + min-width: 100%; + padding: 2rem 1rem; + text-align: center; + width: 100%; + } +} diff --git a/app/modules/external-apps/external-app.service.coffee b/app/modules/external-apps/external-app.service.coffee new file mode 100644 index 00000000..2a301d13 --- /dev/null +++ b/app/modules/external-apps/external-app.service.coffee @@ -0,0 +1,14 @@ +class ExternalAppsService extends taiga.Service + @.$inject = [ + "tgResources" + ] + + constructor: (@rs) -> + + getApplicationToken: (applicationId, state) -> + return @rs.externalapps.getApplicationToken(applicationId, state) + + authorizeApplicationToken: (applicationId, state) -> + return @rs.externalapps.authorizeApplicationToken(applicationId, state) + +angular.module("taigaExternalApps").service("tgExternalAppsService", ExternalAppsService) diff --git a/app/modules/external-apps/external-app.service.spec.coffee b/app/modules/external-apps/external-app.service.spec.coffee new file mode 100644 index 00000000..92af8108 --- /dev/null +++ b/app/modules/external-apps/external-app.service.spec.coffee @@ -0,0 +1,44 @@ +describe "tgExternalAppsService", -> + externalAppsService = provide = null + mocks = {} + + _mockTgResources = () -> + mocks.tgResources = { + externalapps: { + getApplicationToken: sinon.stub() + authorizeApplicationToken: sinon.stub() + } + } + + provide.value "tgResources", mocks.tgResources + + _inject = (callback) -> + inject (_tgExternalAppsService_) -> + externalAppsService = _tgExternalAppsService_ + callback() if callback + + _mocks = () -> + module ($provide) -> + provide = $provide + _mockTgResources() + return null + + _setup = -> + _mocks() + + beforeEach -> + module "taigaExternalApps" + _setup() + _inject() + + it "getApplicationToken", () -> + expect(mocks.tgResources.externalapps.getApplicationToken.callCount).to.be.equal(0) + externalAppsService.getApplicationToken(6, "testing-state") + expect(mocks.tgResources.externalapps.getApplicationToken.callCount).to.be.equal(1) + expect(mocks.tgResources.externalapps.getApplicationToken.calledWith(6, "testing-state")).to.be.true + + it "authorizeApplicationToken", () -> + expect(mocks.tgResources.externalapps.authorizeApplicationToken.callCount).to.be.equal(0) + externalAppsService.authorizeApplicationToken(6, "testing-state") + expect(mocks.tgResources.externalapps.authorizeApplicationToken.callCount).to.be.equal(1) + expect(mocks.tgResources.externalapps.authorizeApplicationToken.calledWith(6, "testing-state")).to.be.true diff --git a/app/modules/external-apps/external-apps.module.coffee b/app/modules/external-apps/external-apps.module.coffee new file mode 100644 index 00000000..4db50ec7 --- /dev/null +++ b/app/modules/external-apps/external-apps.module.coffee @@ -0,0 +1 @@ +module = angular.module("taigaExternalApps", []) diff --git a/app/modules/navigation-bar/navigation-bar.directive.coffee b/app/modules/navigation-bar/navigation-bar.directive.coffee index e4da024d..b941b398 100644 --- a/app/modules/navigation-bar/navigation-bar.directive.coffee +++ b/app/modules/navigation-bar/navigation-bar.directive.coffee @@ -1,4 +1,4 @@ -NavigationBarDirective = (currentUserService, $location) -> +NavigationBarDirective = (currentUserService, navigationBarService, $location) -> link = (scope, el, attrs, ctrl) -> scope.vm = {} @@ -10,6 +10,8 @@ NavigationBarDirective = (currentUserService, $location) -> taiga.defineImmutableProperty(scope.vm, "projects", () -> currentUserService.projects.get("recents")) taiga.defineImmutableProperty(scope.vm, "isAuthenticated", () -> currentUserService.isAuthenticated()) + taiga.defineImmutableProperty(scope.vm, "isEnabledHeader", () -> navigationBarService.isEnabledHeader()) + directive = { templateUrl: "navigation-bar/navigation-bar.html" @@ -21,6 +23,7 @@ NavigationBarDirective = (currentUserService, $location) -> NavigationBarDirective.$inject = [ "tgCurrentUserService", + "tgNavigationBarService" "$location" ] diff --git a/app/modules/navigation-bar/navigation-bar.jade b/app/modules/navigation-bar/navigation-bar.jade index e1b842c9..f8202abf 100644 --- a/app/modules/navigation-bar/navigation-bar.jade +++ b/app/modules/navigation-bar/navigation-bar.jade @@ -1,4 +1,4 @@ -nav.navbar +nav.navbar(ng-if="vm.isEnabledHeader") div.nav-left a.logo( href="#", diff --git a/app/modules/navigation-bar/navigation-bar.service.coffee b/app/modules/navigation-bar/navigation-bar.service.coffee new file mode 100644 index 00000000..48e52e1b --- /dev/null +++ b/app/modules/navigation-bar/navigation-bar.service.coffee @@ -0,0 +1,15 @@ +class NavigationBarService extends taiga.Service + + constructor: -> + @.disableHeader() + + enableHeader: -> + @.enabledHeader = true + + disableHeader: -> + @.enabledHeader = false + + isEnabledHeader: -> + return @.enabledHeader + +angular.module("taigaNavigationBar").service("tgNavigationBarService", NavigationBarService) diff --git a/app/modules/resources/external-apps-resource.service.coffee b/app/modules/resources/external-apps-resource.service.coffee new file mode 100644 index 00000000..f43d14aa --- /dev/null +++ b/app/modules/resources/external-apps-resource.service.coffee @@ -0,0 +1,27 @@ +Resource = (urlsService, http) -> + service = {} + + service.getApplicationToken = (applicationId, state) -> + url = urlsService.resolve("applications") + url = "#{url}/#{applicationId}/token?state=#{state}" + return http.get(url).then (result) -> + Immutable.fromJS(result.data) + + service.authorizeApplicationToken = (applicationId, state) -> + url = urlsService.resolve("application-tokens") + url = "#{url}/authorize" + data = { + "state": state + "application": applicationId + } + + return http.post(url, data).then (result) -> + Immutable.fromJS(result.data) + + return () -> + return {"externalapps": service} + +Resource.$inject = ["$tgUrls", "$tgHttp"] + +module = angular.module("taigaResources2") +module.factory("tgExternalAppsResource", Resource) diff --git a/app/modules/resources/resources.coffee b/app/modules/resources/resources.coffee index b3fecee0..8d73ce4d 100644 --- a/app/modules/resources/resources.coffee +++ b/app/modules/resources/resources.coffee @@ -3,7 +3,8 @@ services = [ "tgUsersResources", "tgUserstoriesResource", "tgTasksResource", - "tgIssuesResource" + "tgIssuesResource", + "tgExternalAppsResource" ] Resources = ($injector) -> diff --git a/app/modules/services/app-meta.service.coffee b/app/modules/services/app-meta.service.coffee index ca31dcb5..eee818b0 100644 --- a/app/modules/services/app-meta.service.coffee +++ b/app/modules/services/app-meta.service.coffee @@ -59,5 +59,13 @@ class AppMetaService extends taiga.Service = -> @.setTwitterMetas(title, description) @.setOpenGraphMetas(title, description) + addMobileViewport: () -> + $('head').append( + '' + ) + + removeMobileViewport: () -> + $('meta[name="viewport"]').remove() + angular.module("taigaCommon").service("tgAppMetaService", AppMetaService) diff --git a/app/partials/auth/login.jade b/app/partials/auth/login.jade index fadbd2f2..4cb2950d 100644 --- a/app/partials/auth/login.jade +++ b/app/partials/auth/login.jade @@ -1,6 +1,8 @@ doctype html -include ../includes/components/beta +div + include ../includes/components/beta + div.wrapper div.login-main div.login-container diff --git a/app/styles/components/beta.scss b/app/styles/components/beta.scss index 4e720fdf..6856d586 100644 --- a/app/styles/components/beta.scss +++ b/app/styles/components/beta.scss @@ -1,6 +1,6 @@ .beta { left: 0; position: absolute; - top: -40px; + top: 0; z-index: 9999; } diff --git a/app/styles/dependencies/responsive.scss b/app/styles/dependencies/responsive.scss index 94218c7f..e426b4e6 100644 --- a/app/styles/dependencies/responsive.scss +++ b/app/styles/dependencies/responsive.scss @@ -12,8 +12,7 @@ @else if $point == tablet { @media (max-width: 767px) { @content ; } } - @else if $point == mobileonly { + @else if $point == mobile { @media (max-width: 480px) { @content ; } - } } diff --git a/app/svg/logo-color.svg b/app/svg/logo-color.svg new file mode 100644 index 00000000..3ba218cd --- /dev/null +++ b/app/svg/logo-color.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/app/svg/logo.svg b/app/svg/logo.svg index 346d8115..ef01257e 100644 --- a/app/svg/logo.svg +++ b/app/svg/logo.svg @@ -1,6 +1,6 @@ - +