1
0
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:
Sascha Pfeiffer 2025-04-06 07:22:38 +00:00
commit de8cd62f48
23 changed files with 429 additions and 50 deletions

View File

@ -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:

View File

@ -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()
}
}
/**

View File

@ -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",

View File

@ -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

View 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>
);
})
}

View File

@ -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))) /

View File

@ -47,6 +47,7 @@ const TotpCircle = (props) => {
);
setToken(newToken);
} catch (e) {
console.log(e);
setToken(t("INVALID"));
}
const percentage =

View File

@ -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" },

View File

@ -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>

View File

@ -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;

View 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;

View File

@ -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;
}

View File

@ -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
*

View File

@ -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 });

View File

@ -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");
}
/**

View File

@ -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 }),

View File

@ -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>&nbsp;
{state.description}
</div>

View File

@ -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={{

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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 && \