Submodule jslib contains modified content diff --git a/jslib/angular/src/components/register.component.ts b/jslib/angular/src/components/register.component.ts index fd91af29..abcfd62c 100644 --- a/jslib/angular/src/components/register.component.ts +++ b/jslib/angular/src/components/register.component.ts @@ -30,7 +30,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn formPromise: Promise; masterPasswordScore: number; referenceData: ReferenceEventRequest; - showTerms = true; + showTerms = false; acceptPolicies: boolean = false; protected successRoute = 'login'; @@ -43,7 +43,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn protected passwordGenerationService: PasswordGenerationService, environmentService: EnvironmentService, protected logService: LogService) { super(environmentService, i18nService, platformUtilsService); - this.showTerms = !platformUtilsService.isSelfHost(); + this.showTerms = false; } async ngOnInit() { @@ -81,6 +81,12 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn } async submit() { + if (typeof crypto.subtle === 'undefined') { + this.platformUtilsService.showToast('error', "This browser requires HTTPS to use the web vault", + "Check the Vaultwarden wiki for details on how to enable it"); + return; + } + if (!this.acceptPolicies && this.showTerms) { this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), this.i18nService.t('acceptPoliciesError')); diff --git a/jslib/angular/src/components/sso.component.ts b/jslib/angular/src/components/sso.component.ts index 1ab8e2f4..7e74fbd7 100644 --- a/jslib/angular/src/components/sso.component.ts +++ b/jslib/angular/src/components/sso.component.ts @@ -23,6 +23,8 @@ import { Utils } from 'jslib-common/misc/utils'; import { AuthResult } from 'jslib-common/models/domain/authResult'; +import { switchMap } from 'rxjs/operators'; + @Directive() export class SsoComponent { identifier: string; @@ -54,13 +56,19 @@ export class SsoComponent { async ngOnInit() { this.route.queryParams.pipe(first()).subscribe(async qParams => { - if (qParams.code != null && qParams.state != null) { + // I have no idea why the qParams is empty here - I've hacked in an alternative very messily, but it works. + const workingParams = (new URL(window.location.href)).searchParams; + const workingSwap = { + code: workingParams.get('code'), + state: workingParams.get('state'), + }; + if (workingSwap.code != null && workingSwap.state != null) { const codeVerifier = await this.storageService.get(ConstantsService.ssoCodeVerifierKey); const state = await this.storageService.get(ConstantsService.ssoStateKey); await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); await this.storageService.remove(ConstantsService.ssoStateKey); - if (qParams.code != null && codeVerifier != null && state != null && this.checkState(state, qParams.state)) { - await this.logIn(qParams.code, codeVerifier, this.getOrgIdentifierFromState(qParams.state)); + if (workingSwap.code != null && codeVerifier != null && state != null && this.checkState(state, workingSwap.state)) { + await this.logIn(workingSwap.code, codeVerifier, this.getOrgIdentifierFromState(workingSwap.state)); } } else if (qParams.clientId != null && qParams.redirectUri != null && qParams.state != null && qParams.codeChallenge != null) { @@ -125,7 +133,7 @@ export class SsoComponent { let authorizeUrl = this.environmentService.getIdentityUrl() + '/connect/authorize?' + 'client_id=' + this.clientId + '&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + 'response_type=code&scope=api offline_access&' + - 'state=' + state + '&code_challenge=' + codeChallenge + '&' + + 'state=' + encodeURIComponent(state) + '&code_challenge=' + codeChallenge + '&' + 'code_challenge_method=S256&response_mode=query&' + 'domain_hint=' + encodeURIComponent(this.identifier); diff --git a/jslib/common/src/abstractions/api.service.ts b/jslib/common/src/abstractions/api.service.ts index 1c6aa0ef..aab45eeb 100644 --- a/jslib/common/src/abstractions/api.service.ts +++ b/jslib/common/src/abstractions/api.service.ts @@ -38,6 +38,7 @@ import { OrganizationSsoRequest } from '../models/request/organization/organizat import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; +import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest'; import { OrganizationSubscriptionUpdateRequest } from '../models/request/organizationSubscriptionUpdateRequest'; import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; @@ -148,6 +149,7 @@ import { SendAccessResponse } from '../models/response/sendAccessResponse'; import { SendFileDownloadDataResponse } from '../models/response/sendFileDownloadDataResponse'; import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse'; import { SendResponse } from '../models/response/sendResponse'; +import { SsoConfigResponse } from '../models/response/ssoConfigResponse'; import { SubscriptionResponse } from '../models/response/subscriptionResponse'; import { SyncResponse } from '../models/response/syncResponse'; import { TaxInfoResponse } from '../models/response/taxInfoResponse'; @@ -386,6 +388,8 @@ export abstract class ApiService { getOrganizationSso: (id: string) => Promise; postOrganization: (request: OrganizationCreateRequest) => Promise; putOrganization: (id: string, request: OrganizationUpdateRequest) => Promise; + getSsoConfig: (id: string) => Promise; + putOrganizationSso: (id: string, request: OrganizationSsoUpdateRequest) => Promise; putOrganizationTaxInfo: (id: string, request: OrganizationTaxInfoUpdateRequest) => Promise; postLeaveOrganization: (id: string) => Promise; postOrganizationLicense: (data: FormData) => Promise; diff --git a/jslib/common/src/models/request/organizationSsoUpdateRequest.ts b/jslib/common/src/models/request/organizationSsoUpdateRequest.ts new file mode 100644 index 00000000..7075aecc --- /dev/null +++ b/jslib/common/src/models/request/organizationSsoUpdateRequest.ts @@ -0,0 +1,8 @@ +export class OrganizationSsoUpdateRequest { + useSso: boolean; + callbackPath: string; + signedOutCallbackPath: string; + authority: string; + clientId: string; + clientSecret: string; +} diff --git a/jslib/common/src/models/request/tokenRequest.ts b/jslib/common/src/models/request/tokenRequest.ts index 41797eb0..26206356 100644 --- a/jslib/common/src/models/request/tokenRequest.ts +++ b/jslib/common/src/models/request/tokenRequest.ts @@ -14,9 +14,10 @@ export class TokenRequest implements CaptchaProtectedRequest { clientId: string; clientSecret: string; device?: DeviceRequest; + orgId?: string constructor(credentials: string[], codes: string[], clientIdClientSecret: string[], public provider: TwoFactorProviderType, - public token: string, public remember: boolean, public captchaResponse: string, device?: DeviceRequest) { + public token: string, public remember: boolean, public captchaResponse: string, device?: DeviceRequest, orgId?: string) { if (credentials != null && credentials.length > 1) { this.email = credentials[0]; this.masterPasswordHash = credentials[1]; @@ -28,6 +29,9 @@ export class TokenRequest implements CaptchaProtectedRequest { this.clientId = clientIdClientSecret[0]; this.clientSecret = clientIdClientSecret[1]; } + if (orgId && orgId !== '') { + this.orgId = orgId; + } this.device = device != null ? device : null; } @@ -50,6 +54,7 @@ export class TokenRequest implements CaptchaProtectedRequest { obj.code = this.code; obj.code_verifier = this.codeVerifier; obj.redirect_uri = this.redirectUri; + obj.org_identifier = this.orgId; } else { throw new Error('must provide credentials or codes'); } diff --git a/jslib/common/src/models/response/ssoConfigResponse.ts b/jslib/common/src/models/response/ssoConfigResponse.ts new file mode 100644 index 00000000..9c72dd33 --- /dev/null +++ b/jslib/common/src/models/response/ssoConfigResponse.ts @@ -0,0 +1,22 @@ +import { BaseResponse } from './baseResponse'; + +export class SsoConfigResponse extends BaseResponse { + id: string; + useSso: boolean; + callbackPath: string; + signedOutCallbackPath: string; + authority: string; + clientId: string; + clientSecret: string; + + constructor(response: any) { + super(response); + this.id = this.getResponseProperty('Id'); + this.useSso = this.getResponseProperty('UseSso'); + this.callbackPath = this.getResponseProperty('CallbackPath'); + this.signedOutCallbackPath = this.getResponseProperty('SignedOutCallbackPath'); + this.authority = this.getResponseProperty('Authority'); + this.clientId = this.getResponseProperty('ClientId'); + this.clientSecret = this.getResponseProperty('ClientSecret'); + } +} diff --git a/jslib/common/src/services/api.service.ts b/jslib/common/src/services/api.service.ts index 46fdc139..16140f6c 100644 --- a/jslib/common/src/services/api.service.ts +++ b/jslib/common/src/services/api.service.ts @@ -39,6 +39,7 @@ import { OrganizationSsoRequest } from '../models/request/organization/organizat import { OrganizationCreateRequest } from '../models/request/organizationCreateRequest'; import { OrganizationImportRequest } from '../models/request/organizationImportRequest'; import { OrganizationKeysRequest } from '../models/request/organizationKeysRequest'; +import { OrganizationSsoUpdateRequest } from '../models/request/organizationSsoUpdateRequest'; import { OrganizationSubscriptionUpdateRequest } from '../models/request/organizationSubscriptionUpdateRequest'; import { OrganizationTaxInfoUpdateRequest } from '../models/request/organizationTaxInfoUpdateRequest'; import { OrganizationUpdateRequest } from '../models/request/organizationUpdateRequest'; @@ -154,6 +155,7 @@ import { SendAccessResponse } from '../models/response/sendAccessResponse'; import { SendFileDownloadDataResponse } from '../models/response/sendFileDownloadDataResponse'; import { SendFileUploadDataResponse } from '../models/response/sendFileUploadDataResponse'; import { SendResponse } from '../models/response/sendResponse'; +import { SsoConfigResponse } from '../models/response/ssoConfigResponse'; import { SubscriptionResponse } from '../models/response/subscriptionResponse'; import { SyncResponse } from '../models/response/syncResponse'; import { TaxInfoResponse } from '../models/response/taxInfoResponse'; @@ -1187,6 +1189,16 @@ export class ApiService implements ApiServiceAbstraction { return new OrganizationResponse(r); } + async getSsoConfig(id: string): Promise { + const r = await this.send('GET', '/organizations/' + id + '/sso', null, true, true); + return new SsoConfigResponse(r); + } + + async putOrganizationSso(id: string, request: OrganizationSsoUpdateRequest): Promise { + const r = await this.send('PUT', '/organizations/' + id + '/sso', request, true, false); + return new SsoConfigResponse(r); + } + async putOrganizationTaxInfo(id: string, request: OrganizationTaxInfoUpdateRequest): Promise { return this.send('PUT', '/organizations/' + id + '/tax', request, true, false); } diff --git a/jslib/common/src/services/auth.service.ts b/jslib/common/src/services/auth.service.ts index e4f670d7..d96f78cd 100644 --- a/jslib/common/src/services/auth.service.ts +++ b/jslib/common/src/services/auth.service.ts @@ -310,13 +310,13 @@ export class AuthService implements AuthServiceAbstraction { let request: TokenRequest; if (twoFactorToken != null && twoFactorProvider != null) { request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, twoFactorProvider, - twoFactorToken, remember, captchaToken, deviceRequest); + twoFactorToken, remember, captchaToken, deviceRequest, orgId); } else if (storedTwoFactorToken != null) { request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, - TwoFactorProviderType.Remember, storedTwoFactorToken, false, captchaToken, deviceRequest); + TwoFactorProviderType.Remember, storedTwoFactorToken, false, captchaToken, deviceRequest, orgId); } else { request = new TokenRequest(emailPassword, codeCodeVerifier, clientIdClientSecret, null, - null, false, captchaToken, deviceRequest); + null, false, captchaToken, deviceRequest, orgId); } const response = await this.apiService.postIdentityToken(request); diff --git a/src/404.html b/src/404.html index eba36375..cb8883ec 100644 --- a/src/404.html +++ b/src/404.html @@ -41,10 +41,10 @@

You can return to the web vault, check our status page - or contact us.

+ or contact us.

diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f01ecb69..22fd7dc2 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -160,6 +160,10 @@ export class AppComponent implements OnDestroy, OnInit { } break; case 'showToast': + if (typeof message.text === "string" && typeof crypto.subtle === 'undefined') { + message.title="This browser requires HTTPS to use the web vault"; + message.text="Check the Vaultwarden wiki for details on how to enable it"; + } this.showToast(message); break; case 'setFullWidth': diff --git a/src/app/layouts/footer.component.html b/src/app/layouts/footer.component.html index b001b9e3..c1bd2ac8 100644 --- a/src/app/layouts/footer.component.html +++ b/src/app/layouts/footer.component.html @@ -1,7 +1,7 @@