mirror of
https://gitlab.com/psono/psono-client
synced 2025-04-19 03:22:16 +03:00
Merge branch 'develop' into 'master'
Preparing v4.1.1 See merge request psono/psono-client!280
This commit is contained in:
commit
de8cd62f48
@ -116,6 +116,35 @@ build-docker-image:
|
||||
- /^v[0-9]*\.[0-9]*\.[0-9]*$/
|
||||
|
||||
|
||||
build-sbom:
|
||||
except:
|
||||
- schedules
|
||||
stage: build
|
||||
image: psono-docker.jfrog.io/ubuntu:22.04
|
||||
script:
|
||||
- apt-get update && apt-get install -y curl
|
||||
- sh ./var/prep-build.sh
|
||||
- npx @cyclonedx/cyclonedx-npm > sbom.json
|
||||
- >
|
||||
if [ ! -z "$artifactory_credentials" ]; then
|
||||
curl -fL https://getcli.jfrog.io | sh &&
|
||||
./jfrog config add rt-server-1 --artifactory-url=https://psono.jfrog.io/psono --user=gitlab --password=$artifactory_credentials --interactive=false &&
|
||||
./jfrog rt u --target-props="CI_COMMIT_REF_NAME=$CI_COMMIT_REF_NAME;CI_COMMIT_SHA=$CI_COMMIT_SHA;CI_COMMIT_URL=$CI_PROJECT_URL/commit/$CI_COMMIT_SHA;CI_PROJECT_ID=$CI_PROJECT_ID;CI_PROJECT_NAME=$CI_PROJECT_NAME;CI_PROJECT_NAMESPACE=$CI_PROJECT_NAMESPACE;CI_PROJECT_URL=$CI_PROJECT_URL;CI_PIPELINE_ID=$CI_PIPELINE_ID;CI_PIPELINE_URL=$CI_PROJECT_URL/pipelines/$CI_PIPELINE_ID;CI_COMMIT_REF_NAME=$CI_COMMIT_REF_NAME;CI_JOB_ID=$CI_JOB_ID;CI_JOB_URL=$CI_PROJECT_URL/-/jobs/$CI_JOB_ID;CI_JOB_NAME=$CI_JOB_NAME;CI_JOB_STAGE=$CI_JOB_STAGE;CI_RUNNER_ID=$CI_RUNNER_ID;GITLAB_USER_ID=$GITLAB_USER_ID;CI_SERVER_VERSION=$CI_SERVER_VERSION" /builds/psono/psono-client/sbom.json psono/client/$CI_COMMIT_REF_NAME/client-sbom.json &&
|
||||
./jfrog rt sp "psono/client/$CI_COMMIT_REF_NAME/client-sbom.json" "CI_COMMIT_TAG=$CI_COMMIT_TAG" || true
|
||||
fi
|
||||
- mv /builds/psono/psono-client/sbom.json ../
|
||||
- rm -Rf *
|
||||
- rm -Rf .* 2> /dev/null || true
|
||||
- mv ../sbom.json ./
|
||||
artifacts:
|
||||
name: "sbom_$CI_COMMIT_REF_NAME"
|
||||
paths:
|
||||
- ./*
|
||||
only:
|
||||
- branches@psono/psono-client
|
||||
- /^v[0-9]*\.[0-9]*\.[0-9]*$/
|
||||
|
||||
|
||||
build-firefox-extension:
|
||||
except:
|
||||
- schedules
|
||||
@ -410,6 +439,7 @@ release-artifacts:
|
||||
- apt-get install -y curl
|
||||
- curl -fL https://getcli.jfrog.io | sh
|
||||
- ./jfrog config add rt-server-1 --artifactory-url=https://psono.jfrog.io/psono --user=gitlab --password=$artifactory_credentials --interactive=false
|
||||
- ./jfrog rt cp --flat psono/client/$CI_COMMIT_REF_NAME/client-sbom.json psono/client/latest/
|
||||
- ./jfrog rt cp --flat psono/client/$CI_COMMIT_REF_NAME/firefox-extension.zip psono/client/latest/
|
||||
- ./jfrog rt cp --flat psono/client/$CI_COMMIT_REF_NAME/chrome-extension.zip psono/client/latest/
|
||||
- ./jfrog rt cp --flat psono/client/$CI_COMMIT_REF_NAME/webclient.zip psono/client/latest/
|
||||
@ -584,6 +614,8 @@ deploy-nightlyartifacts:
|
||||
- schedules
|
||||
stage: release
|
||||
image: psono-docker.jfrog.io/ubuntu:22.04
|
||||
dependencies:
|
||||
- build-sbom
|
||||
script:
|
||||
- sh ./var/deploy_nightlyartifacts.sh
|
||||
environment:
|
||||
@ -598,6 +630,8 @@ deploy-releaseartifacts:
|
||||
- schedules
|
||||
stage: deploy
|
||||
image: psono-docker.jfrog.io/ubuntu:22.04
|
||||
dependencies:
|
||||
- build-sbom
|
||||
script:
|
||||
- sh ./var/deploy_releaseartifacts.sh
|
||||
environment:
|
||||
|
@ -299,7 +299,7 @@ const ClassWorkerContentScript = function (base, browser, setTimeout) {
|
||||
let buttonContent = otherButtons[i].tagName.toLowerCase() === "input" ? otherButtons[i].value : otherButtons[i].innerText;
|
||||
|
||||
if (passwordSubmitButtonLabels.has(buttonContent.toLowerCase().trim())) {
|
||||
submitButtons.append(otherButtons[i]);
|
||||
submitButtons.push(otherButtons[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1044,6 +1044,108 @@ const ClassWorkerContentScript = function (base, browser, setTimeout) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parses an URL and returns an object with all details separated
|
||||
*
|
||||
* @param {String} url The url to be parsed
|
||||
* @returns {object} Returns the split up url
|
||||
*/
|
||||
function parseUrl(url) {
|
||||
const empty = {
|
||||
scheme: null,
|
||||
authority: null,
|
||||
authority_without_www: null,
|
||||
base_url: null,
|
||||
full_domain: null,
|
||||
full_domain_without_www: null,
|
||||
port: null,
|
||||
path: null,
|
||||
query: null,
|
||||
fragment: null
|
||||
};
|
||||
if (!url) {
|
||||
return empty;
|
||||
}
|
||||
|
||||
if (!url.includes("://")) {
|
||||
// Its supposed to be an url but doesn't include a schema so let's prefix it with http://
|
||||
url = 'http://' + url;
|
||||
}
|
||||
|
||||
let parsedUrl;
|
||||
try {
|
||||
parsedUrl = new URL(url);
|
||||
} catch (e) {
|
||||
return empty;
|
||||
}
|
||||
|
||||
return {
|
||||
scheme: parsedUrl.protocol.slice(0,-1),
|
||||
base_url: parsedUrl.protocol + '//' + parsedUrl.host,
|
||||
authority: parsedUrl.host,
|
||||
authority_without_www: parsedUrl.host ? parsedUrl.host.replace(/^(www\.)/, ""): parsedUrl.host, //remove leading www.
|
||||
full_domain: parsedUrl.hostname,
|
||||
full_domain_without_www: parsedUrl.hostname ? parsedUrl.hostname.replace(/^(www\.)/, "") : parsedUrl.hostname,
|
||||
port: parsedUrl.port || null,
|
||||
path: parsedUrl.pathname,
|
||||
query: parsedUrl.search || null,
|
||||
fragment: parsedUrl.hash ? parsedUrl.hash.substring(1) : null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a provided urlfilter and authority fit together
|
||||
*
|
||||
* @param {string} authority The "authority" of the current website, e.g. www.example.com:80
|
||||
* @param {string} urlFilter The url filter, e.g. *.example.com or www.example.com
|
||||
*
|
||||
* @returns {boolean} Whether the string ends with the suffix or not
|
||||
*/
|
||||
function isUrlFilterMatch(authority, urlFilter) {
|
||||
if (!authority || !urlFilter) {
|
||||
return false
|
||||
}
|
||||
authority = authority.toLowerCase();
|
||||
urlFilter = urlFilter.toLowerCase();
|
||||
let directMatch = authority === urlFilter;
|
||||
let wildcardMatch = urlFilter.startsWith('*.') && authority.endsWith(urlFilter.substring(1));
|
||||
|
||||
return directMatch || wildcardMatch
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the function that returns whether a certain leaf entry should be considered a possible condidate
|
||||
* for a provided url
|
||||
*
|
||||
* @param {string} url The url to match
|
||||
*
|
||||
* @returns {(function(*): (boolean|*))|*}
|
||||
*/
|
||||
const getSearchWebsitePasswordsByUrlfilter = function (url) {
|
||||
const parsedUrl = parseUrl(url);
|
||||
|
||||
const filter = function (leaf) {
|
||||
|
||||
if (typeof leaf.website_password_url_filter === "undefined") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (leaf.website_password_url_filter) {
|
||||
const urlFilters = leaf.website_password_url_filter.split(/\s+|,|;/);
|
||||
for (let i = 0; i < urlFilters.length; i++) {
|
||||
if (!isUrlFilterMatch(parsedUrl.authority, urlFilters[i])) {
|
||||
continue;
|
||||
}
|
||||
return parsedUrl.scheme === 'https' || (leaf.hasOwnProperty("allow_http") && leaf["allow_http"]);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return filter;
|
||||
};
|
||||
|
||||
/**
|
||||
* handles password request answer
|
||||
*
|
||||
@ -1051,36 +1153,61 @@ const ClassWorkerContentScript = function (base, browser, setTimeout) {
|
||||
* @param sender
|
||||
* @param sendResponse
|
||||
*/
|
||||
function onReturnSecret(data, sender, sendResponse) {
|
||||
for (let i = 0; i < myForms.length; i++) {
|
||||
if (
|
||||
(myForms[i].username && myForms[i].username.isEqualNode(lastRequestElement)) ||
|
||||
(myForms[i].password && myForms[i].password.isEqualNode(lastRequestElement)) ||
|
||||
fillAll
|
||||
) {
|
||||
fillFieldHelper(myForms[i].username, data.website_password_username);
|
||||
fillFieldHelper(myForms[i].password, data.website_password_password);
|
||||
async function onReturnSecret(data, sender, sendResponse) {
|
||||
|
||||
for (let ii = 0; ii < dropInstances.length; ii++) {
|
||||
dropInstances[ii].close();
|
||||
}
|
||||
dropInstances = [];
|
||||
if (!fillAll) {
|
||||
break;
|
||||
const isIframe = window !== window.top;
|
||||
|
||||
function autofill() {
|
||||
for (let i = 0; i < myForms.length; i++) {
|
||||
if (
|
||||
(myForms[i].username && myForms[i].username.isEqualNode(lastRequestElement)) ||
|
||||
(myForms[i].password && myForms[i].password.isEqualNode(lastRequestElement)) ||
|
||||
fillAll
|
||||
) {
|
||||
fillFieldHelper(myForms[i].username, data.website_password_username);
|
||||
fillFieldHelper(myForms[i].password, data.website_password_password);
|
||||
|
||||
for (let ii = 0; ii < dropInstances.length; ii++) {
|
||||
dropInstances[ii].close();
|
||||
}
|
||||
dropInstances = [];
|
||||
if (!fillAll) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.hasOwnProperty('custom_fields') && data.custom_fields) {
|
||||
for (let i = 0; i < data.custom_fields.length; i++) {
|
||||
const field = findFieldByName(data.custom_fields[i].name);
|
||||
if (field) {
|
||||
fillFieldHelper(field, data.custom_fields[i].value);
|
||||
if (data.hasOwnProperty('custom_fields') && data.custom_fields) {
|
||||
for (let i = 0; i < data.custom_fields.length; i++) {
|
||||
const field = findFieldByName(data.custom_fields[i].name);
|
||||
if (field) {
|
||||
fillFieldHelper(field, data.custom_fields[i].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fillAll = false;
|
||||
}
|
||||
|
||||
fillAll=false;
|
||||
if (isIframe) {
|
||||
const filter = getSearchWebsitePasswordsByUrlfilter(window.location.origin);
|
||||
if (!filter(data)) {
|
||||
const parsedUrl = parseUrl(window.location.origin);
|
||||
const autofillId = uuid.v4();
|
||||
await base.emit("approve-iframe-login", {
|
||||
'authority': parsedUrl.authority,
|
||||
'autofill_id': autofillId
|
||||
}, (response) => {
|
||||
if (response.data) {
|
||||
autofill()
|
||||
}
|
||||
});
|
||||
} else {
|
||||
autofill()
|
||||
}
|
||||
} else {
|
||||
autofill()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,8 @@
|
||||
{
|
||||
"APPROVE_IFRAME_LOGIN": "Different origin detected",
|
||||
"APPROVE_IFRAME_LOGIN_DESCRIPTION": "The form is hosted on an origin that is not in your url filter. Choose approve to autofill anyway or cancel to stop. To avoid this message from popping up again, open advanced and add {{origin}} to your url filters.",
|
||||
"CONFIG_JSON_MALFORMED": "The json format of your the config.json seems to be malformed. Once fixed make sure to test in an private / incognito browser tab.",
|
||||
"INVALID_TOTP_PARAMETERS": "Invalid TOTP parameters.",
|
||||
"ACCOUNT_DELETED_SUCCESSFULLY": "Your user account with all its data has been deleted successfully.",
|
||||
"REQUEST_TO_DELETE_USER_ACCOUNT_SUCCESSFUL": "The request to delete your user account was successful. Please check your inbox for an email with the verification link.",
|
||||
"TAGS": "Tags",
|
||||
|
@ -13,6 +13,8 @@ import InputAdornment from "@mui/material/InputAdornment";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||
import SelectFieldTotpAlgorithm from "../select-field/totp-algorithm";
|
||||
import cryptoLibraryService from "../../services/crypto-library";
|
||||
import GridContainerErrors from "../grid-container-errors";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
textField: {
|
||||
@ -24,6 +26,7 @@ const DialogAddTotp = (props) => {
|
||||
const { open, onClose } = props;
|
||||
const { t } = useTranslation();
|
||||
const classes = useStyles();
|
||||
const [errors, setErrors] = useState([]);
|
||||
const [totpPeriod, setTotpPeriod] = useState(30);
|
||||
const [totpAlgorithm, setTotpAlgorithm] = useState("SHA1");
|
||||
const [totpDigits, setTotpDigits] = useState(6);
|
||||
@ -34,12 +37,23 @@ const DialogAddTotp = (props) => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
const onSave = (event) => {
|
||||
onClose(
|
||||
totpPeriod,
|
||||
totpAlgorithm,
|
||||
totpDigits,
|
||||
totpCode,
|
||||
)
|
||||
try {
|
||||
cryptoLibraryService.getTotpToken(
|
||||
totpCode,
|
||||
totpPeriod,
|
||||
totpAlgorithm,
|
||||
totpDigits
|
||||
);
|
||||
onClose(
|
||||
totpPeriod,
|
||||
totpAlgorithm,
|
||||
totpDigits,
|
||||
totpCode,
|
||||
)
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setErrors(["INVALID_TOTP_PARAMETERS"]);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -156,6 +170,7 @@ const DialogAddTotp = (props) => {
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<GridContainerErrors errors={errors} setErrors={setErrors} />
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
|
41
src/js/components/notification-snackbar.js
Normal file
41
src/js/components/notification-snackbar.js
Normal file
@ -0,0 +1,41 @@
|
||||
import * as React from 'react';
|
||||
import { useSelector } from "react-redux";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import Snackbar from '@mui/material/Snackbar';
|
||||
import Alert from '@mui/material/Alert';
|
||||
|
||||
import notification from '../services/notification';
|
||||
|
||||
export default function NotificationSnackbar() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const messages = useSelector(state => state.notification.messages);
|
||||
|
||||
return messages.map((message, index) => {
|
||||
|
||||
const handleClose = (event, reason) => {
|
||||
const newMessages = [...messages];
|
||||
newMessages.splice(index, 1);
|
||||
notification.set(newMessages);
|
||||
};
|
||||
return (
|
||||
<Snackbar
|
||||
anchorOrigin={{ 'vertical': 'top', 'horizontal': 'center' }}
|
||||
open={message.text !== ''}
|
||||
//autoHideDuration={6000}
|
||||
onClose={handleClose}
|
||||
key={index}
|
||||
>
|
||||
<Alert
|
||||
onClose={handleClose}
|
||||
severity={message.type}
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{t(message.text)}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
);
|
||||
})
|
||||
|
||||
}
|
@ -62,14 +62,19 @@ const TextFieldTotp = (props) => {
|
||||
codeRef.current = code;
|
||||
|
||||
const updateToken = () => {
|
||||
setToken(
|
||||
cryptoLibraryService.getTotpToken(
|
||||
let newToken;
|
||||
try {
|
||||
newToken = cryptoLibraryService.getTotpToken(
|
||||
codeRef.current,
|
||||
periodRef.current,
|
||||
algorithmRef.current,
|
||||
digitsRef.current
|
||||
)
|
||||
);
|
||||
);
|
||||
setToken(newToken);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setToken(t("INVALID"));
|
||||
}
|
||||
const percentage =
|
||||
100 -
|
||||
(((periodRef.current || 30) - (Math.round(new Date().getTime() / 1000.0) % (periodRef.current || 30))) /
|
||||
|
@ -47,6 +47,7 @@ const TotpCircle = (props) => {
|
||||
);
|
||||
setToken(newToken);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
setToken(t("INVALID"));
|
||||
}
|
||||
const percentage =
|
||||
|
@ -25,7 +25,7 @@ const languages = {
|
||||
no: { code: "no", lng_code: "LANG_NO", lng_title_native: "Norsk", active: true },
|
||||
pl: { code: "pl", lng_code: "LANG_PL", lng_title_native: "Polskie", active: true },
|
||||
pt: { code: "pt", lng_code: "LANG_PT_PT", lng_title_native: "Portuguese", active: true },
|
||||
"pt-br": { code: "pt-br", lng_code: "LANG_PT_BR", lng_title_native: "Portuguese (BR)", active: true },
|
||||
"pt-BR": { code: "pt-BR", lng_code: "LANG_PT_BR", lng_title_native: "Portuguese (BR)", active: true },
|
||||
ru: { code: "ru", lng_code: "LANG_RU", lng_title_native: "Русский", active: true },
|
||||
sv: { code: "sv", lng_code: "LANG_SV", lng_title_native: "Svenska", active: true },
|
||||
sk: { code: "sk", lng_code: "LANG_SK", lng_title_native: "Slovák" },
|
||||
|
@ -21,6 +21,7 @@ initSentry();
|
||||
|
||||
import IndexView from "./views/index";
|
||||
import DownloadBanner from "./components/download-banner";
|
||||
import NotificationSnackbar from "./components/notification-snackbar";
|
||||
import browserClientService from "./services/browser-client";
|
||||
import backgroundService from "./services/background";
|
||||
|
||||
@ -92,6 +93,7 @@ async function initAndRenderApp() {
|
||||
<LazyThemeProvider>
|
||||
<HashRouter history={customHistory} hashType="hashbang">
|
||||
<DownloadBanner />
|
||||
<NotificationSnackbar />
|
||||
<IndexView />
|
||||
</HashRouter>
|
||||
</LazyThemeProvider>
|
||||
|
@ -6,7 +6,7 @@ import user from "./user";
|
||||
import settingsDatastore from "./settings-datastore";
|
||||
import server from "./server";
|
||||
import client from "./client";
|
||||
// import notification from './notification';
|
||||
import notification from './notification';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
persistent,
|
||||
@ -16,7 +16,7 @@ const rootReducer = combineReducers({
|
||||
settingsDatastore,
|
||||
server,
|
||||
client,
|
||||
// notification
|
||||
notification
|
||||
});
|
||||
|
||||
export default rootReducer;
|
||||
|
29
src/js/reducers/notification.js
Normal file
29
src/js/reducers/notification.js
Normal file
@ -0,0 +1,29 @@
|
||||
import { NOTIFICATION_SEND, NOTIFICATION_SET } from '../actions/action-types';
|
||||
|
||||
function notification(
|
||||
state = {
|
||||
messages: [],
|
||||
},
|
||||
action
|
||||
) {
|
||||
switch (action.type) {
|
||||
case NOTIFICATION_SEND:
|
||||
const new_messages = state.messages;
|
||||
new_messages.push({
|
||||
text: action.message,
|
||||
type: action.messageType,
|
||||
});
|
||||
|
||||
return Object.assign({}, state, {
|
||||
messages: new_messages,
|
||||
});
|
||||
case NOTIFICATION_SET:
|
||||
return Object.assign({}, state, {
|
||||
messages: action.messages,
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default notification;
|
@ -26,6 +26,7 @@ const defaultComplianceDisableShares = false;
|
||||
const defaultComplianceDisableRecoveryCodes = false;
|
||||
const defaultComplianceEnforce2fa = false;
|
||||
const defaultComplianceEnforceCentralSecurityReports = false;
|
||||
const defaultComplianceEnforceBreachDetection = false;
|
||||
const defaultComplianceMinMasterPasswordComplexity = 2;
|
||||
const defaultComplianceMinMasterPasswordLength = 14;
|
||||
const defaultComplianceClipboardClearDelay = 30;
|
||||
@ -92,6 +93,7 @@ function server(
|
||||
complianceDisableRecoveryCodes: defaultComplianceDisableRecoveryCodes,
|
||||
complianceEnforce2fa: defaultComplianceEnforce2fa,
|
||||
complianceEnforceCentralSecurityReports: defaultComplianceEnforceCentralSecurityReports,
|
||||
complianceEnforceBreachDetection: defaultComplianceEnforceBreachDetection,
|
||||
complianceMinMasterPasswordComplexity: defaultComplianceMinMasterPasswordComplexity,
|
||||
complianceMinMasterPasswordLength: defaultComplianceMinMasterPasswordLength,
|
||||
complianceClipboardClearDelay: defaultComplianceClipboardClearDelay,
|
||||
@ -156,6 +158,7 @@ function server(
|
||||
complianceDisableRecoveryCodes: defaultComplianceDisableRecoveryCodes,
|
||||
complianceEnforce2fa: defaultComplianceEnforce2fa,
|
||||
complianceEnforceCentralSecurityReports: defaultComplianceEnforceCentralSecurityReports,
|
||||
complianceEnforceBreachDetection: defaultComplianceEnforceBreachDetection,
|
||||
complianceMinMasterPasswordComplexity: defaultComplianceMinMasterPasswordComplexity,
|
||||
complianceMinMasterPasswordLength: defaultComplianceMinMasterPasswordLength,
|
||||
complianceClipboardClearDelay: defaultComplianceClipboardClearDelay,
|
||||
@ -219,6 +222,7 @@ function server(
|
||||
complianceDisableRecoveryCodes: action.info.compliance_disable_recovery_codes,
|
||||
complianceEnforce2fa: action.info.compliance_enforce_2fa,
|
||||
complianceEnforceCentralSecurityReports: action.info.compliance_enforce_central_security_reports,
|
||||
complianceEnforceBreachDetection: typeof(action.info.compliance_enforce_breach_detection) === "undefined" ? defaultComplianceEnforceBreachDetection : action.info.compliance_enforce_breach_detection,
|
||||
complianceMinMasterPasswordComplexity: action.info.compliance_min_master_password_complexity,
|
||||
complianceMinMasterPasswordLength: action.info.compliance_min_master_password_length,
|
||||
complianceClipboardClearDelay: typeof(action.info.compliance_clipboard_clear_delay) === "undefined" ? defaultComplianceClipboardClearDelay : action.info.compliance_clipboard_clear_delay,
|
||||
@ -266,6 +270,9 @@ function server(
|
||||
if (action.policy.hasOwnProperty('compliance_enforce_central_security_reports')) {
|
||||
data['complianceEnforceCentralSecurityReports'] = action.policy.compliance_enforce_central_security_reports;
|
||||
}
|
||||
if (action.policy.hasOwnProperty('compliance_enforce_breach_detection')) {
|
||||
data['complianceEnforceBreachDetection'] = action.policy.compliance_enforce_breach_detection;
|
||||
}
|
||||
if (action.policy.hasOwnProperty('compliance_central_security_reports_recurrence_interval')) {
|
||||
data['complianceCentralSecurityReportsRecurrenceInterval'] = action.policy.compliance_central_security_reports_recurrence_interval;
|
||||
}
|
||||
|
@ -332,6 +332,7 @@ function fillSecretTab(secretId, tab) {
|
||||
|
||||
const onSuccess = function (content) {
|
||||
if (leaf.type === 'website_password') {
|
||||
|
||||
browserClient.emitTab(tab.id, "fillpassword", {
|
||||
username: content.website_password_username,
|
||||
password: content.website_password_password,
|
||||
@ -394,6 +395,7 @@ function onMessage(request, sender, sendResponse) {
|
||||
"request-secret": onRequestSecret,
|
||||
"open-tab": onOpenTab,
|
||||
"generate-password": onGeneratePassword,
|
||||
"approve-iframe-login": approveIframeLogin,
|
||||
"login-form-submit": loginFormSubmit,
|
||||
"oidc-saml-redirect-detected": oidcSamlRedirectDetected,
|
||||
"decrypt-gpg": decryptPgp,
|
||||
@ -770,7 +772,7 @@ function onWebsitePasswordRefresh(request, sender, sendResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
searchWebsitePasswordsByUrlfilter(sender.url, false).then(function (leafs) {
|
||||
searchWebsitePasswordsByUrlfilter(sender.tab.url, false).then(function (leafs) {
|
||||
const update = [];
|
||||
|
||||
for (let ii = 0; ii < leafs.length; ii++) {
|
||||
@ -1398,6 +1400,35 @@ function onAuthRequired(details, callbackFn) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Being fired once a content script wants to ask a user to approve iframe login
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function approveIframeLogin(request, sender, sendResponse) {
|
||||
notificationBarService.create(
|
||||
i18n.t("APPROVE_IFRAME_LOGIN"),
|
||||
i18n.t("APPROVE_IFRAME_LOGIN_DESCRIPTION", {'origin': request.data.origin}),
|
||||
[
|
||||
{
|
||||
title: i18n.t("ALLOW"),
|
||||
onClick: () => {
|
||||
sendResponse({ event: "approve-iframe-login-response", data: true });
|
||||
},
|
||||
color: "primary",
|
||||
},
|
||||
{
|
||||
title: i18n.t("CANCEL"),
|
||||
onClick: () => {
|
||||
sendResponse({ event: "approve-iframe-login-response", data: false });
|
||||
},
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
return true; // Important, do not remove! Otherwise Async return wont work
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the last login credentials in the datastore
|
||||
*
|
||||
|
@ -8,6 +8,7 @@ const _ = require('lodash');
|
||||
import helperService from "./helper";
|
||||
import { getStore } from "./store";
|
||||
import deviceService from "./device";
|
||||
import notification from "./notification";
|
||||
|
||||
const theme = {
|
||||
"palette": {
|
||||
@ -481,6 +482,13 @@ function _loadConfig() {
|
||||
const standardizeConfig = function (newConfig, url) {
|
||||
const parsed_url = helperService.parseUrl(url);
|
||||
|
||||
if (!newConfig.hasOwnProperty("backend_servers")) {
|
||||
newConfig["backend_servers"] = [];
|
||||
}
|
||||
if (newConfig["backend_servers"].length === 0) {
|
||||
newConfig["backend_servers"].push({});
|
||||
}
|
||||
|
||||
if (!newConfig.hasOwnProperty("theme")) {
|
||||
newConfig["theme"] = theme;
|
||||
} else {
|
||||
@ -614,7 +622,18 @@ function _loadConfig() {
|
||||
|
||||
if (remoteConfigJson === null) {
|
||||
fetch("config.json").then(async (response) => {
|
||||
return onSuccess({ data: await response.json() });
|
||||
|
||||
let configJson;
|
||||
|
||||
try {
|
||||
configJson = await response.json();
|
||||
console.log(configJson);
|
||||
} catch (e) {
|
||||
notification.errorSend("CONFIG_JSON_MALFORMED")
|
||||
return onSuccess({ data: {}});
|
||||
}
|
||||
|
||||
return onSuccess({ data: configJson });
|
||||
})
|
||||
} else {
|
||||
return onSuccess({ data: remoteConfigJson });
|
||||
|
@ -17,7 +17,7 @@ function infoSend(message) {
|
||||
* @param {array} message The message to send
|
||||
*/
|
||||
function errorSend(message) {
|
||||
action().sendNotification(message, "danger");
|
||||
action().sendNotification(message, "error");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,12 +92,21 @@ export const initStore = async () => {
|
||||
},
|
||||
}
|
||||
},
|
||||
4: (state) => {
|
||||
return {
|
||||
...state,
|
||||
server: {
|
||||
...state.server,
|
||||
complianceEnforceBreachDetection: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const persistConfig = {
|
||||
key: await accountService.getCurrentId(),
|
||||
blacklist: ['transient'],
|
||||
version: 3,
|
||||
blacklist: ['transient', 'notification'],
|
||||
version: 4,
|
||||
storage: storageService.get('state'),
|
||||
debug: false,
|
||||
migrate: createMigrate(migrations, { debug: false }),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, {useState} from "react";
|
||||
import React, {useState, useRef, useEffect} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { makeStyles } from '@mui/styles';
|
||||
@ -31,6 +31,11 @@ const useStyles = makeStyles((theme) => ({
|
||||
textContainerCell: {
|
||||
display: "table-cell",
|
||||
verticalAlign: "middle",
|
||||
maxHeight: "40px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
fontSize: "clamp(10px, 1rem, 16px)", // Responsive font size between 10px and 16px
|
||||
lineHeight: "1.2", // Tighter line height to fit in the box
|
||||
},
|
||||
textContainerTitle: {
|
||||
fontWeight: "bold",
|
||||
@ -70,13 +75,48 @@ const NotificationBarView = (props) => {
|
||||
const [state, setState] = useState({
|
||||
buttons: []
|
||||
});
|
||||
const textContainerRef = useRef(null);
|
||||
const [textStyle, setTextStyle] = useState({});
|
||||
|
||||
// Scale text to fit container
|
||||
const adjustTextSize = () => {
|
||||
if (!textContainerRef.current) return;
|
||||
|
||||
const container = textContainerRef.current;
|
||||
const containerHeight = 44; // Maximum height
|
||||
|
||||
// Reset font size for accurate measurement
|
||||
container.style.fontSize = '';
|
||||
|
||||
// Check if content overflows
|
||||
if (container.scrollHeight > containerHeight) {
|
||||
// Calculate ratio to scale down
|
||||
const ratio = containerHeight / container.scrollHeight;
|
||||
const newSize = Math.max(10, Math.floor(parseFloat(getComputedStyle(container).fontSize) * ratio));
|
||||
|
||||
setTextStyle({
|
||||
fontSize: `${newSize}px`,
|
||||
lineHeight: '1.2',
|
||||
});
|
||||
} else {
|
||||
setTextStyle({});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Initial load
|
||||
React.useEffect(() => {
|
||||
browserClient.emitSec("notification-bar-loaded", {}, function(result) {
|
||||
setState(result)
|
||||
})
|
||||
setState(result);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Adjust text size when content changes
|
||||
React.useEffect(() => {
|
||||
if (state.id) {
|
||||
// Allow DOM to update before measuring
|
||||
setTimeout(adjustTextSize, 0);
|
||||
}
|
||||
}, [state.title, state.description]);
|
||||
|
||||
const buttonClick = (index) => {
|
||||
browserClient.emitSec("notification-bar-button-click", {
|
||||
@ -107,7 +147,11 @@ const NotificationBarView = (props) => {
|
||||
</div>
|
||||
</Hidden>
|
||||
<div className={classes.textContainer}>
|
||||
<div className={classes.textContainerCell}>
|
||||
<div
|
||||
ref={textContainerRef}
|
||||
className={classes.textContainerCell}
|
||||
style={textStyle}
|
||||
>
|
||||
<span className={classes.textContainerTitle}>{state.title}:</span>
|
||||
{state.description}
|
||||
</div>
|
||||
|
@ -110,7 +110,8 @@ const SecurityReportView = (props) => {
|
||||
const [errors, setErrors] = useState([]);
|
||||
const [msgs, setMsgs] = useState([]);
|
||||
const [reportComplete, setReportComplete] = useState(false);
|
||||
const [checkHaveibeenpwned, setCheckHaveibeenpwned] = useState(false);
|
||||
|
||||
const [checkHaveibeenpwned, setCheckHaveibeenpwned] = useState(getStore().getState().server.complianceEnforceBreachDetection);
|
||||
const [analysis, setAnalysis] = useState({
|
||||
'passwords': []
|
||||
});
|
||||
@ -600,8 +601,13 @@ const SecurityReportView = (props) => {
|
||||
<Checkbox
|
||||
checked={checkHaveibeenpwned}
|
||||
onChange={(event) => {
|
||||
setCheckHaveibeenpwned(event.target.checked);
|
||||
if (getStore().getState().server.complianceEnforceBreachDetection) {
|
||||
setCheckHaveibeenpwned(true);
|
||||
} else {
|
||||
setCheckHaveibeenpwned(event.target.checked);
|
||||
}
|
||||
}}
|
||||
disabled={getStore().getState().server.complianceEnforceBreachDetection}
|
||||
checkedIcon={<Check className={classes.checkedIcon} />}
|
||||
icon={<Check className={classes.uncheckedIcon} />}
|
||||
classes={{
|
||||
|
@ -21,6 +21,7 @@ gsutil cp psono.amd64.deb gs://get.psono.com/$CI_PROJECT_PATH/nightly/psono.amd6
|
||||
gsutil cp psono.x86_64.exe gs://get.psono.com/$CI_PROJECT_PATH/nightly/psono.x86_64.exe && \
|
||||
gsutil cp psono.x86_64.msi gs://get.psono.com/$CI_PROJECT_PATH/nightly/psono.x86_64.msi && \
|
||||
gsutil cp psono.dmg gs://get.psono.com/$CI_PROJECT_PATH/nightly/psono.dmg && \
|
||||
gsutil cp sbom.json gs://get.psono.com/$CI_PROJECT_PATH/nightly/sbom.json && \
|
||||
gsutil cp firefox-extension.zip gs://get.psono.com/$CI_PROJECT_PATH/nightly/firefox-extension.zip && \
|
||||
gsutil cp chrome-extension.zip gs://get.psono.com/$CI_PROJECT_PATH/nightly/chrome-extension.zip && \
|
||||
gsutil cp webclient.zip gs://get.psono.com/$CI_PROJECT_PATH/nightly/webclient.zip
|
||||
|
@ -21,6 +21,7 @@ gsutil cp psono.amd64.deb gs://get.psono.com/$CI_PROJECT_PATH/latest/psono.amd64
|
||||
gsutil cp psono.x86_64.exe gs://get.psono.com/$CI_PROJECT_PATH/latest/psono.x86_64.exe && \
|
||||
gsutil cp psono.x86_64.msi gs://get.psono.com/$CI_PROJECT_PATH/latest/psono.x86_64.msi && \
|
||||
gsutil cp psono.dmg gs://get.psono.com/$CI_PROJECT_PATH/latest/psono.dmg && \
|
||||
gsutil cp sbom.json gs://get.psono.com/$CI_PROJECT_PATH/latest/sbom.json && \
|
||||
gsutil cp firefox-extension.zip gs://get.psono.com/$CI_PROJECT_PATH/latest/firefox-extension.zip && \
|
||||
gsutil cp chrome-extension.zip gs://get.psono.com/$CI_PROJECT_PATH/latest/chrome-extension.zip && \
|
||||
gsutil cp webclient.zip gs://get.psono.com/$CI_PROJECT_PATH/latest/webclient.zip && \
|
||||
@ -29,6 +30,7 @@ gsutil cp psono.amd64.deb gs://get.psono.com/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAM
|
||||
gsutil cp psono.x86_64.exe gs://get.psono.com/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME/psono.x86_64.exe && \
|
||||
gsutil cp psono.x86_64.msi gs://get.psono.com/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME/psono.x86_64.msi && \
|
||||
gsutil cp psono.dmg gs://get.psono.com/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME/psono.dmg && \
|
||||
gsutil cp sbom.json gs://get.psono.com/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME/sbom.json && \
|
||||
gsutil cp firefox-extension.zip gs://get.psono.com/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME/firefox-extension.zip && \
|
||||
gsutil cp chrome-extension.zip gs://get.psono.com/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME/chrome-extension.zip && \
|
||||
gsutil cp webclient.zip gs://get.psono.com/$CI_PROJECT_PATH/$CI_COMMIT_REF_NAME/webclient.zip
|
||||
|
@ -19,7 +19,7 @@ wget "https://psono.jfrog.io/psono/psono/client/languages/locale-nl.json" -outfi
|
||||
wget "https://psono.jfrog.io/psono/psono/client/languages/locale-no.json" -outfile "src/common/data/translations/locale-no.json"
|
||||
wget "https://psono.jfrog.io/psono/psono/client/languages/locale-pl.json" -outfile "src/common/data/translations/locale-pl.json"
|
||||
wget "https://psono.jfrog.io/psono/psono/client/languages/locale-pt.json" -outfile "src/common/data/translations/locale-pt.json"
|
||||
wget "https://psono.jfrog.io/psono/psono/client/languages/locale-pt-br.json" -outfile "src/common/data/translations/locale-pt-br.json"
|
||||
wget "https://psono.jfrog.io/psono/psono/client/languages/locale-pt-br.json" -outfile "src/common/data/translations/locale-pt-BR.json"
|
||||
wget "https://psono.jfrog.io/psono/psono/client/languages/locale-ru.json" -outfile "src/common/data/translations/locale-ru.json"
|
||||
wget "https://psono.jfrog.io/psono/psono/client/languages/locale-sv.json" -outfile "src/common/data/translations/locale-sv.json"
|
||||
wget "https://psono.jfrog.io/psono/psono/client/languages/locale-sk.json" -outfile "src/common/data/translations/locale-sk.json"
|
||||
|
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
curl -o src/common/data/translations/locale-da.json https://psono.jfrog.io/psono/psono/client/languages/locale-da.json && \
|
||||
curl -o src/common/data/translations/locale-ca.json https://psono.jfrog.io/psono/psono/client/languages/locale-ca.json && \
|
||||
curl -o src/common/data/translations/locale-sv.json https://psono.jfrog.io/psono/psono/client/languages/locale-sv.json && \
|
||||
@ -20,7 +22,7 @@ curl -o src/common/data/translations/locale-ja.json https://psono.jfrog.io/psono
|
||||
curl -o src/common/data/translations/locale-ko.json https://psono.jfrog.io/psono/psono/client/languages/locale-ko.json && \
|
||||
curl -o src/common/data/translations/locale-nl.json https://psono.jfrog.io/psono/psono/client/languages/locale-nl.json && \
|
||||
curl -o src/common/data/translations/locale-pt.json https://psono.jfrog.io/psono/psono/client/languages/locale-pt.json && \
|
||||
curl -o src/common/data/translations/locale-pt-br.json https://psono.jfrog.io/psono/psono/client/languages/locale-pt-br.json && \
|
||||
curl -o src/common/data/translations/locale-pt-BR.json https://psono.jfrog.io/psono/psono/client/languages/locale-pt-br.json && \
|
||||
curl -o src/common/data/translations/locale-pl.json https://psono.jfrog.io/psono/psono/client/languages/locale-pl.json && \
|
||||
curl -o src/common/data/translations/locale-ru.json https://psono.jfrog.io/psono/psono/client/languages/locale-ru.json && \
|
||||
curl -o src/common/data/translations/locale-sk.json https://psono.jfrog.io/psono/psono/client/languages/locale-sk.json && \
|
||||
|
Loading…
x
Reference in New Issue
Block a user