mirror of
https://github.com/bitwarden/web
synced 2025-12-06 00:03:28 +00:00
Compare commits
15 Commits
authreq
...
feature/cm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac8bd7292e | ||
|
|
2a4f6415d6 | ||
|
|
91eb60ebef | ||
|
|
29476f6744 | ||
|
|
ff7151fbbd | ||
|
|
4415720f3f | ||
|
|
c49a9aa330 | ||
|
|
189b4437d4 | ||
|
|
d22f17fc81 | ||
|
|
6efe992680 | ||
|
|
a1cde3c820 | ||
|
|
79b6f3595e | ||
|
|
d0c0db70c5 | ||
|
|
f05b9439cc | ||
|
|
371c21553c |
4
.github/workflows/version-bump.yml
vendored
4
.github/workflows/version-bump.yml
vendored
@@ -27,13 +27,13 @@ jobs:
|
||||
ref: version_bump_${{ github.event.inputs.version_number }}
|
||||
|
||||
- name: Bump Version - package.json
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
uses: bitwarden/gh-actions/version-bump@0c263b3963211ccaf5804313c3b3a0bcc52d4b19
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./package.json"
|
||||
|
||||
- name: Bump Version - package-lock.json
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
uses: bitwarden/gh-actions/version-bump@0c263b3963211ccaf5804313c3b3a0bcc52d4b19
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./package-lock.json"
|
||||
|
||||
2
jslib
2
jslib
Submodule jslib updated: 27a37dc34b...462a4d7c56
@@ -89,20 +89,6 @@
|
||||
<i class="fa fa-pencil-square-o" aria-hidden="true"></i> {{ "createAccount" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="button"
|
||||
(click)="startPasswordlessLogin()"
|
||||
class="btn btn-outline-secondary btn-block btn-submit mt-2"
|
||||
>
|
||||
<span> <i class="fa fa-key" aria-hidden="true"></i> Passwordless</span>
|
||||
<i
|
||||
class="fa fa-spinner fa-spin"
|
||||
title="{{ 'loading' | i18n }}"
|
||||
aria-hidden="true"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<a routerLink="/sso" class="btn btn-outline-secondary btn-block mt-2">
|
||||
<i class="fa fa-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
|
||||
|
||||
@@ -4,15 +4,11 @@ import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { AppIdService } from "jslib-common/abstractions/appId.service";
|
||||
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||
import { I18nService } from "jslib-common/abstractions/i18n.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService } from "jslib-common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "jslib-common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "jslib-common/abstractions/policy.service";
|
||||
@@ -39,14 +35,10 @@ export class LoginComponent extends BaseLoginComponent {
|
||||
environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
apiService: ApiService,
|
||||
private apiService: ApiService,
|
||||
private policyService: PolicyService,
|
||||
logService: LogService,
|
||||
ngZone: NgZone,
|
||||
appIdService: AppIdService,
|
||||
broadcasterService: BroadcasterService,
|
||||
cryptoService: CryptoService,
|
||||
messagingService: MessagingService
|
||||
ngZone: NgZone
|
||||
) {
|
||||
super(
|
||||
authService,
|
||||
@@ -58,12 +50,7 @@ export class LoginComponent extends BaseLoginComponent {
|
||||
passwordGenerationService,
|
||||
cryptoFunctionService,
|
||||
logService,
|
||||
ngZone,
|
||||
apiService,
|
||||
appIdService,
|
||||
broadcasterService,
|
||||
cryptoService,
|
||||
messagingService
|
||||
ngZone
|
||||
);
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ import { DomSanitizer } from "@angular/platform-browser";
|
||||
import { NavigationEnd, Router } from "@angular/router";
|
||||
import * as jq from "jquery";
|
||||
import { IndividualConfig, ToastrService } from "ngx-toastr";
|
||||
import Swal, { SweetAlertIcon } from "sweetalert2";
|
||||
import Swal from "sweetalert2";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { AuthService } from "jslib-common/abstractions/auth.service";
|
||||
import { BroadcasterService } from "jslib-common/abstractions/broadcaster.service";
|
||||
import { CipherService } from "jslib-common/abstractions/cipher.service";
|
||||
@@ -40,10 +39,6 @@ import { SendOptionsPolicy } from "./organizations/policies/send-options.compone
|
||||
import { SingleOrgPolicy } from "./organizations/policies/single-org.component";
|
||||
import { TwoFactorAuthenticationPolicy } from "./organizations/policies/two-factor-authentication.component";
|
||||
|
||||
import { Utils } from "jslib-common/misc/utils";
|
||||
import { AuthRequestUpdateRequest } from "jslib-common/models/request/authRequestUpdateRequest";
|
||||
import { AppIdService } from "jslib-common/abstractions/appId.service";
|
||||
|
||||
const BroadcasterSubscriptionId = "AppComponent";
|
||||
const IdleTimeout = 60000 * 10; // 10 minutes
|
||||
|
||||
@@ -81,9 +76,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
private eventService: EventService,
|
||||
private policyService: PolicyService,
|
||||
protected policyListService: PolicyListService,
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private apiService: ApiService,
|
||||
private appIdService: AppIdService
|
||||
private keyConnectorService: KeyConnectorService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -174,49 +167,6 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
this.keyConnectorService.setConvertAccountRequired(true);
|
||||
this.router.navigate(["/remove-password"]);
|
||||
break;
|
||||
case "closeDialog":
|
||||
Swal.close();
|
||||
break;
|
||||
case "authRequest":
|
||||
Swal.close();
|
||||
const authRequestId = message.id;
|
||||
console.log("Got request for " + authRequestId);
|
||||
const authRequest = await this.apiService.getAuthRequest(authRequestId);
|
||||
const email = await this.stateService.getEmail();
|
||||
const requestPublicKey = Utils.fromB64ToArray(authRequest.publicKey).buffer;
|
||||
const fingerprint = await this.cryptoService.getFingerprint(email, requestPublicKey);
|
||||
const authRequestConfirmed = await this.platformUtilsService.showDialog(
|
||||
`Another device is requesting to log into your Bitwarden account. Do you want to allow it?<br /><br />
|
||||
<b><u>Details</u></b><br />
|
||||
Device: Opera Browser<br />
|
||||
IP Address: ${authRequest.requestIpAddress}<br />
|
||||
Time: ${authRequest.creationDate}<br /><br />
|
||||
<b><u>Public Key Fingerprint</u></b><br />
|
||||
<code>${fingerprint.join("-")}</code>`,
|
||||
"New Login Request",
|
||||
"Yes, Allow",
|
||||
"No, Deny",
|
||||
null,
|
||||
true
|
||||
);
|
||||
if (authRequestConfirmed) {
|
||||
const masterKey = await this.cryptoService.getKey();
|
||||
const masterKeyHash = await this.cryptoService.getKeyHash();
|
||||
const encMasterKey = await this.cryptoService.rsaEncrypt(
|
||||
masterKey.key,
|
||||
requestPublicKey
|
||||
);
|
||||
const encMasterKeyHash = await this.cryptoService.rsaEncrypt(
|
||||
Utils.fromB64ToArray(masterKeyHash).buffer,
|
||||
requestPublicKey
|
||||
);
|
||||
const authRequestUpdate = new AuthRequestUpdateRequest();
|
||||
authRequestUpdate.deviceIdentifier = await this.appIdService.getAppId();
|
||||
authRequestUpdate.key = encMasterKey.encryptedString;
|
||||
authRequestUpdate.masterPasswordHash = encMasterKeyHash.encryptedString;
|
||||
this.apiService.putAuthRequest(authRequest.id, authRequestUpdate);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ToastrModule } from "ngx-toastr";
|
||||
import { BroadcasterMessagingService } from "../../services/broadcasterMessaging.service";
|
||||
import { HtmlStorageService } from "../../services/htmlStorage.service";
|
||||
import { I18nService } from "../../services/i18n.service";
|
||||
import { KeyConnectorService } from "../../services/keyConnector.service";
|
||||
import { MemoryStorageService } from "../../services/memoryStorage.service";
|
||||
import { PasswordRepromptService } from "../../services/passwordReprompt.service";
|
||||
import { StateService } from "../../services/state.service";
|
||||
@@ -40,6 +41,7 @@ import { EventService as EventLoggingServiceAbstraction } from "jslib-common/abs
|
||||
import { FolderService as FolderServiceAbstraction } from "jslib-common/abstractions/folder.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "jslib-common/abstractions/i18n.service";
|
||||
import { ImportService as ImportServiceAbstraction } from "jslib-common/abstractions/import.service";
|
||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "jslib-common/abstractions/keyConnector.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { MessagingService as MessagingServiceAbstraction } from "jslib-common/abstractions/messaging.service";
|
||||
import { NotificationsService as NotificationsServiceAbstraction } from "jslib-common/abstractions/notifications.service";
|
||||
@@ -190,6 +192,10 @@ export function initFactory(
|
||||
provide: PasswordRepromptServiceAbstraction,
|
||||
useClass: PasswordRepromptService,
|
||||
},
|
||||
{
|
||||
provide: KeyConnectorServiceAbstraction,
|
||||
useClass: KeyConnectorService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ServicesModule {}
|
||||
|
||||
@@ -587,7 +587,6 @@
|
||||
id="idEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
inputmode="email"
|
||||
name="Identity.Email"
|
||||
[(ngModel)]="cipher.identity.email"
|
||||
appInputVerbatim
|
||||
@@ -600,7 +599,6 @@
|
||||
id="idPhone"
|
||||
class="form-control"
|
||||
type="text"
|
||||
inputmode="tel"
|
||||
name="Identity.Phone"
|
||||
[(ngModel)]="cipher.identity.phone"
|
||||
[disabled]="cipher.isDeleted || viewOnly"
|
||||
|
||||
12
src/connectors/cme.html
Normal file
12
src/connectors/cme.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"
|
||||
/>
|
||||
<title>Bitwarden CME Connector</title>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
135
src/connectors/cme.ts
Normal file
135
src/connectors/cme.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { KeyConnectorUserKeyResponse } from "jslib-common/models/response/keyConnectorUserKeyResponse";
|
||||
import { b64Decode, getQsParam } from "./common";
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
init();
|
||||
});
|
||||
|
||||
let parentUrl: string = null;
|
||||
let parentOrigin: string = null;
|
||||
let sentSuccess = false;
|
||||
|
||||
async function init() {
|
||||
await start();
|
||||
onMessage();
|
||||
}
|
||||
|
||||
async function start() {
|
||||
sentSuccess = false;
|
||||
|
||||
const data = getQsParam("data");
|
||||
if (!data) {
|
||||
error("No data.");
|
||||
return;
|
||||
}
|
||||
|
||||
parentUrl = getQsParam("parent");
|
||||
if (!parentUrl) {
|
||||
error("No parent.");
|
||||
return;
|
||||
} else {
|
||||
parentUrl = decodeURIComponent(parentUrl);
|
||||
parentOrigin = new URL(parentUrl).origin;
|
||||
}
|
||||
|
||||
let decodedData: any;
|
||||
try {
|
||||
decodedData = JSON.parse(b64Decode(data));
|
||||
} catch (e) {
|
||||
error("Cannot parse data.");
|
||||
return;
|
||||
}
|
||||
|
||||
const keyConnectorUrl = new URL(decodedData.url);
|
||||
const bearerAccessToken = decodedData.token;
|
||||
const operation = decodedData.operation;
|
||||
const key = decodedData.key;
|
||||
|
||||
if (keyConnectorUrl.hostname === "vault.bitwarden.com") {
|
||||
error("Invalid hostname.");
|
||||
}
|
||||
|
||||
if (operation === "get") {
|
||||
const getRequest = new Request(keyConnectorUrl.href + "user-keys", {
|
||||
cache: "no-store",
|
||||
method: "GET",
|
||||
headers: new Headers({
|
||||
Accept: "application/json",
|
||||
Authorization: "Bearer " + bearerAccessToken,
|
||||
}),
|
||||
});
|
||||
getRequest.headers.set("Cache-Control", "no-store");
|
||||
getRequest.headers.set("Pragma", "no-cache");
|
||||
|
||||
try {
|
||||
const response = await fetch(getRequest);
|
||||
if (response.status !== 200) {
|
||||
throw new Error();
|
||||
}
|
||||
success(new KeyConnectorUserKeyResponse(await response.json()));
|
||||
} catch {
|
||||
error("Error getting key");
|
||||
return;
|
||||
}
|
||||
} else if (operation === "post") {
|
||||
const postRequest = new Request(keyConnectorUrl.href + "user-keys", {
|
||||
cache: "no-store",
|
||||
method: "POST",
|
||||
headers: new Headers({
|
||||
Accept: "application/json",
|
||||
Authorization: "Bearer " + bearerAccessToken,
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
}),
|
||||
body: JSON.stringify({ key: key }),
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(postRequest);
|
||||
if (response.status !== 200) {
|
||||
throw new Error();
|
||||
}
|
||||
} catch {
|
||||
error("Error posting key");
|
||||
return;
|
||||
}
|
||||
success(null);
|
||||
} else {
|
||||
// TODO: put operation
|
||||
error("Unsupported operation.");
|
||||
}
|
||||
}
|
||||
|
||||
function onMessage() {
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
if (!event.origin || event.origin === "" || event.origin !== parentOrigin) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data === "start") {
|
||||
start();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
function error(message: string) {
|
||||
parent.postMessage("error|" + message, parentUrl);
|
||||
}
|
||||
|
||||
function success(response: KeyConnectorUserKeyResponse) {
|
||||
if (sentSuccess) {
|
||||
return;
|
||||
}
|
||||
parent.postMessage(
|
||||
"success|" + (response != null && response.key != null ? response.key : ""),
|
||||
parentUrl
|
||||
);
|
||||
sentSuccess = true;
|
||||
}
|
||||
|
||||
function info(message: string | object) {
|
||||
parent.postMessage("info|" + JSON.stringify(message), parentUrl);
|
||||
}
|
||||
@@ -30,6 +30,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#cme_iframe {
|
||||
border: none;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.list-group-2fa {
|
||||
.logo-2fa {
|
||||
min-width: 100px;
|
||||
|
||||
105
src/services/keyConnector.service.ts
Normal file
105
src/services/keyConnector.service.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { ApiService } from "jslib-common/abstractions/api.service";
|
||||
import { CryptoService } from "jslib-common/abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "jslib-common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "jslib-common/abstractions/environment.service";
|
||||
import { LogService } from "jslib-common/abstractions/log.service";
|
||||
import { OrganizationService } from "jslib-common/abstractions/organization.service";
|
||||
import { PlatformUtilsService } from "jslib-common/abstractions/platformUtils.service";
|
||||
import { StateService } from "jslib-common/abstractions/state.service";
|
||||
import { TokenService } from "jslib-common/abstractions/token.service";
|
||||
|
||||
import { CMEIFrame } from "jslib-common/misc/cme_iframe";
|
||||
import { KeyConnectorUserKeyRequest } from "jslib-common/models/request/keyConnectorUserKeyRequest";
|
||||
import { KeyConnectorUserKeyResponse } from "jslib-common/models/response/keyConnectorUserKeyResponse";
|
||||
|
||||
import { KeyConnectorService as BaseKeyConnectorService } from "jslib-common/services/keyConnector.service";
|
||||
|
||||
@Injectable()
|
||||
export class KeyConnectorService extends BaseKeyConnectorService {
|
||||
constructor(
|
||||
stateService: StateService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
cryptoService: CryptoService,
|
||||
apiService: ApiService,
|
||||
tokenService: TokenService,
|
||||
logService: LogService,
|
||||
organizationService: OrganizationService,
|
||||
private environmentService: EnvironmentService,
|
||||
private platformUtilsService: PlatformUtilsService
|
||||
) {
|
||||
super(
|
||||
stateService,
|
||||
cryptoFunctionService,
|
||||
cryptoService,
|
||||
apiService,
|
||||
tokenService,
|
||||
logService,
|
||||
organizationService
|
||||
);
|
||||
}
|
||||
|
||||
protected async getUserKeyFromKeyConnector(url: string): Promise<KeyConnectorUserKeyResponse> {
|
||||
if (this.platformUtilsService.isSelfHost()) {
|
||||
return super.getUserKeyFromKeyConnector(url);
|
||||
}
|
||||
|
||||
const frame = this.createIframe();
|
||||
frame.frame.initGet(await this.apiService.getActiveBearerToken(), url);
|
||||
|
||||
return frame.promise.then((key: string) => new KeyConnectorUserKeyResponse({ Key: key }));
|
||||
}
|
||||
|
||||
protected async postUserKeyToKeyConnector(
|
||||
url: string,
|
||||
request: KeyConnectorUserKeyRequest
|
||||
): Promise<void> {
|
||||
if (this.platformUtilsService.isSelfHost()) {
|
||||
return super.postUserKeyToKeyConnector(url, request);
|
||||
}
|
||||
|
||||
const frame = this.createIframe();
|
||||
frame.frame.initPost(await this.apiService.getActiveBearerToken(), url, request.key);
|
||||
|
||||
// tslint:disable-next-line
|
||||
return frame.promise.then(() => {});
|
||||
}
|
||||
|
||||
private createIframe(): { frame: CMEIFrame; promise: Promise<string> } {
|
||||
const el = this.createIframeElement();
|
||||
|
||||
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||
|
||||
let iframe: CMEIFrame;
|
||||
|
||||
const promise: Promise<string> = new Promise(async (resolve) => {
|
||||
iframe = new CMEIFrame(
|
||||
window,
|
||||
webVaultUrl,
|
||||
resolve,
|
||||
(error: string) => {
|
||||
this.platformUtilsService.showToast("error", null, error);
|
||||
},
|
||||
(info: string) => {
|
||||
this.logService.info(info);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
promise.finally(() => el.remove());
|
||||
|
||||
return {
|
||||
frame: iframe,
|
||||
promise: promise,
|
||||
};
|
||||
}
|
||||
|
||||
private createIframeElement() {
|
||||
const el = document.createElement("iframe");
|
||||
el.id = "cme_iframe";
|
||||
document.body.appendChild(el);
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,11 @@ const plugins = [
|
||||
filename: "captcha-mobile-connector.html",
|
||||
chunks: ["connectors/captcha"],
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./src/connectors/cme.html",
|
||||
filename: "cme-connector.html",
|
||||
chunks: ["connectors/cme"],
|
||||
}),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{ from: "./src/.nojekyll" },
|
||||
@@ -221,6 +226,7 @@ const webpackConfig = {
|
||||
"connectors/duo": "./src/connectors/duo.ts",
|
||||
"connectors/sso": "./src/connectors/sso.ts",
|
||||
"connectors/captcha": "./src/connectors/captcha.ts",
|
||||
"connectors/cme": "./src/connectors/cme.ts",
|
||||
theme_head: "./src/theme.js",
|
||||
},
|
||||
externals: {
|
||||
|
||||
Reference in New Issue
Block a user