Skip to content

Commit

Permalink
🐛 fixes issue #134 blank Track/Week tab
Browse files Browse the repository at this point in the history
⬆️ updated package dependencies
🔒 prevent clickjacking attacks by setting X-Frame-Options header - there is a new global setting to allow one particular origins if you need to show titra in an iframe
🔒 enforce password policy to prevent weak passwords
💄 improved dark mode signin/signup pages
  • Loading branch information
faburem committed Jun 12, 2022
1 parent c1fff7e commit e09390c
Show file tree
Hide file tree
Showing 15 changed files with 114 additions and 42 deletions.
3 changes: 2 additions & 1 deletion .meteor/packages
Expand Up @@ -7,7 +7,6 @@
meteor-base@1.5.1 # Packages every Meteor app needs to have
mobile-experience@1.1.0 # Packages for a great mobile UX
mongo@1.15.0 # The database Meteor supports right now
blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
reactive-var@1.0.11 # Reactive variable for tracker
tracker@1.2.0 # Meteor's client-side reactive programming library

Expand Down Expand Up @@ -36,3 +35,5 @@ oauth@2.1.2
accounts-base@2.2.3
accounts-oauth@1.4.1
oauth2@1.3.1
browser-policy-framing
blaze-html-templates
15 changes: 8 additions & 7 deletions .meteor/versions
Expand Up @@ -7,10 +7,12 @@ babel-compiler@7.9.0
babel-runtime@1.5.1
base64@1.0.12
binary-heap@1.0.11
blaze@2.5.0
blaze-html-templates@1.2.1
blaze@2.6.0
blaze-html-templates@2.0.0
blaze-tools@1.1.3
boilerplate-generator@1.7.1
browser-policy-common@1.0.11
browser-policy-framing@1.1.0
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.4.0
Expand Down Expand Up @@ -64,7 +66,7 @@ oauth@2.1.2
oauth2@1.3.1
observe-sequence@1.0.20
ordered-dict@1.1.0
ostrio:flow-router-extra@3.7.5
ostrio:flow-router-extra@3.8.0
ostrio:logger@2.1.1
ostrio:loggerconsole@2.1.0
promise@0.12.0
Expand All @@ -80,17 +82,16 @@ service-configuration@1.3.0
sha@1.0.9
shell-server@0.5.0
socket-stream-client@0.5.0
spacebars@1.2.0
spacebars@1.3.0
spacebars-compiler@1.3.1
standard-minifier-css@1.8.1
standard-minifier-js@2.8.0
templating@1.4.1
templating@1.4.2
templating-compiler@1.4.1
templating-runtime@1.5.0
templating-runtime@1.6.0
templating-tools@1.2.2
tracker@1.2.0
tunguska:reactive-aggregate@1.3.8
ui@1.0.13
underscore@1.0.10
url@1.3.2
webapp@1.13.1
Expand Down
3 changes: 3 additions & 0 deletions imports/api/globalsettings/globalsettings.js
Expand Up @@ -120,4 +120,7 @@ defaultSettings.push({
defaultSettings.push({
name: 'holidayRegion', description: 'settings.holiday_region', type: 'text', value: '',
})
defaultSettings.push({
name: 'XFrameOptionsOrigin', description: 'settings.xframe_options_origin', type: 'text', value: '',
})
export { defaultSettings, Globalsettings }
2 changes: 1 addition & 1 deletion imports/api/timecards/server/publications.js
Expand Up @@ -72,7 +72,7 @@ Meteor.publish('userTimeCardsForPeriodByProjectByTask', function periodTimecards
entries: { $push: '$$ROOT' },
},
},
], { clientCollection: 'clientTimecards' })
], { clientCollection: 'clientTimecards', specificWarnings: { objectId: false } })
})
Meteor.publish('myTimecardsForDate', function myTimecardsForDate({ date }) {
check(date, String)
Expand Down
7 changes: 5 additions & 2 deletions imports/startup/server/startup.js
@@ -1,4 +1,5 @@
import { AccountsAnonymous } from 'meteor/faburem:accounts-anonymous'
import { BrowserPolicy } from 'meteor/browser-policy-framing'
import Extensions from '../../api/extensions/extensions.js'
import { defaultSettings, Globalsettings } from '../../api/globalsettings/globalsettings.js'
import { getGlobalSetting } from '../../utils/frontend_helpers'
Expand All @@ -21,7 +22,7 @@ Meteor.startup(() => {
if (getGlobalSetting('enableOpenIDConnect')) {
import('../../utils/oidc_server').then((Oidc) => {
Oidc.registerOidc()
});
})
}
for (const extension of Extensions.find({})) {
if (extension.isActive) {
Expand All @@ -34,7 +35,9 @@ Meteor.startup(() => {
eval(extension.server)
}
}

if (getGlobalSetting('XFrameOptionsOrigin')) {
BrowserPolicy.framing.restrictToOrigin(getGlobalSetting('XFrameOptionsOrigin'))
}
if (process.env.NODE_ENV !== 'development') {
// eslint-disable-next-line no-console
console.log(`titra started on port ${process.env.PORT}`)
Expand Down
4 changes: 2 additions & 2 deletions imports/ui/pages/register.html
Expand Up @@ -22,12 +22,12 @@ <h3>{{t "login.register"}}</h3>
<input type="email" class="form-control" id="at-field-email" name="at-field-email" placeholder="Email" autocapitalize="none" autocorrect="off" value="{{email}}">
<span class="help-block hide"></span>
</div>
<div class="at-input mb-3 has-feedback">
<div class="at-input mb-3">
<label class="control-label" for="at-field-password">
{{t "login.password"}}
</label>
<input type="password" class="form-control" id="at-field-password" name="at-field-password" placeholder="{{t 'login.password'}}" autocapitalize="none" autocorrect="off">
<span class="help-block hide"></span>
<div class="js-password-feedback invalid-feedback hide"></div>
</div>
<div class="at-input mb-3 has-feedback">
<label class="control-label" for="at-field-password_again">
Expand Down
33 changes: 33 additions & 0 deletions imports/ui/pages/register.js
@@ -1,5 +1,6 @@
import { FlowRouter } from 'meteor/ostrio:flow-router-extra'
import { t } from '../../utils/i18n.js'
import { validateEmail, validatePassword } from '../../utils/frontend_helpers.js'
import './register.html'

Template.register.events({
Expand All @@ -13,6 +14,20 @@ Template.register.events({
return
}
if (templateInstance.$('#at-field-email').val() && templateInstance.$('#at-field-password').val() && templateInstance.$('#at-field-password-again').val()) {
if (!validateEmail(templateInstance.$('#at-field-email').val())) {
templateInstance.$('#at-field-email').addClass('is-invalid')
templateInstance.$('.notification').text(t('login.invalid_email'))
document.querySelector('.notification').classList.toggle('d-none')
return
}
const passwordValidation = validatePassword(templateInstance.$('#at-field-password').val())
if (!passwordValidation.valid) {
templateInstance.$('#at-field-password').addClass('is-invalid')
templateInstance.$('#at-field-password-again').addClass('is-invalid')
templateInstance.$('.notification').text(passwordValidation.message)
document.querySelector('.notification').classList.toggle('d-none')
return
}
Accounts.createUser({
email: templateInstance.$('#at-field-email').val(),
password: templateInstance.$('#at-field-password').val(),
Expand All @@ -32,6 +47,24 @@ Template.register.events({
})
}
},
'keyup #at-field-password': (event, templateInstance) => {
event.preventDefault()
const validetedPW = validatePassword(templateInstance.$('#at-field-password').val())
templateInstance.$('.js-password-feedback').text(validetedPW.message)
if (validetedPW.valid) {
templateInstance.$('#at-field-password').removeClass('is-invalid')
templateInstance.$('#at-field-password-again').removeClass('is-invalid')
templateInstance.$('.js-password-feedback').removeClass('invalid-feedback')
templateInstance.$('.js-password-feedback').addClass('valid-feedback')
templateInstance.$('.js-password-feedback').removeClass('hide')
} else {
templateInstance.$('#at-field-password').addClass('is-invalid')
templateInstance.$('.js-password-feedback').removeClass('valid-feedback')
templateInstance.$('.js-password-feedback').addClass('invalid-feedback')
templateInstance.$('.js-password-feedback').removeClass('hide')
templateInstance.$('.js-password-feedback').addClass('d-block')
}
},
})
Template.register.helpers({
email: () => FlowRouter.getQueryParam('email'),
Expand Down
2 changes: 1 addition & 1 deletion imports/ui/pages/signIn.html
Expand Up @@ -49,7 +49,7 @@ <h3>{{t "login.sign_in"}}</h3>
<div class="at-signup-link mt-2">
<p>
{{t "login.no_account"}}
<a href="/join" id="at-signUp" class="at-link at-signup">{{t "login.register"}}</a>
<a href="#" id="at-signUp" class="at-link at-signup js-register">{{t "login.register"}}</a>
</p>
</div>
{{/unless}}
Expand Down
12 changes: 8 additions & 4 deletions imports/ui/pages/signIn.js
Expand Up @@ -6,10 +6,10 @@ import './signIn.html'

function handleLoginResult(error, templateInstance) {
if (error) {
if(error.message) {
templateInstance.$('.notification').text(error.message)
} else {
if (error.error === 403 || error.error === 400) {
templateInstance.$('.notification').text(t(`login.${error.error}`))
} else {
templateInstance.$('.notification').text(error.message)
}
document.querySelector('.notification').classList.remove('d-none')
} else {
Expand Down Expand Up @@ -51,7 +51,7 @@ function signIn(event, templateInstance) {

Template.signIn.helpers({
isOidcConfigured: () => isOidcConfigured(),
disableUserRegistration: () => getGlobalSetting("disableUserRegistration")
disableUserRegistration: () => getGlobalSetting('disableUserRegistration'),
})

Template.signIn.events({
Expand Down Expand Up @@ -80,4 +80,8 @@ Template.signIn.events({
templateInstance.$('#at-field-email').addClass('is-invalid')
}
},
'click .js-register': (event, templateInstance) => {
event.preventDefault()
FlowRouter.go('register', {}, { email: templateInstance.$('#at-field-email').val() })
},
})
2 changes: 1 addition & 1 deletion imports/ui/styles/dark.scss
Expand Up @@ -4,7 +4,7 @@ $secondary: #272727; // Background-Color for a
$success: #009688;
$info: #86a8a5;
$warning: #cb9800;
$danger: #3b1516;
$danger: #f57c80;
$light: #BDBDBD;
$dark: #212529;
$dark-border: #3a3a3a;
Expand Down
10 changes: 8 additions & 2 deletions imports/ui/translations/de.json
Expand Up @@ -171,7 +171,8 @@
"holiday": "Feiertage",
"holiday_country": "Land",
"holiday_state": "Bundesland",
"holiday_region": "Region/Stadt"
"holiday_region": "Region/Stadt",
"xframe_options_origin": "X-Frame-Options header origin URL"
},
"customer": {
"select_customer": "Kunde auswählen",
Expand Down Expand Up @@ -328,7 +329,12 @@
"password_mismatch": "Die eingegebenen Passwörter stimmen nicht überein. Bitte überprüfen Sie Ihre Eingabe.",
"permanent_account": "Permanenten Zugang erstellen",
"anonymous_account_warning": "Sie verwenden aktuell einen temporären Account. Wenn Sie titra auf einem anderen Gerät verwenden möchten, erstellen Sie bitte einen Benutzer mit E-mail Addresse und Passwort.",
"registration_disabled_warning": "Die Registrierung neuer Benutzer ist auf dieser titra Instanz deaktiviert."
"registration_disabled_warning": "Die Registrierung neuer Benutzer ist auf dieser titra Instanz deaktiviert.",
"invalid_email": "Ungültige E-mail Adresse",
"password_insufficient": "Das gewählte Passwort ist zu schwach. Bitte wählen Sie ein Passwort mit mindestens 8 Zeichen.",
"password_strong": "Passwort Sicherheit: Hoch",
"password_medium": "Passwort Sicherheit: Mittel",
"password_weak": "Passwort Sicherheit: Niedrig"
},
"about": {
"documentation": "Dokumentation",
Expand Down
10 changes: 8 additions & 2 deletions imports/ui/translations/en.json
Expand Up @@ -172,7 +172,8 @@
"holiday": "Holiday",
"holiday_country": "Holiday Country",
"holiday_state": "Holiday State",
"holiday_region": "Holyday Region"
"holiday_region": "Holyday Region",
"xframe_options_origin": "X-Frame-Options header origin URL"
},
"customer": {
"select_customer": "Select a customer",
Expand Down Expand Up @@ -322,7 +323,12 @@
"permanent_account": "Create permanent account",
"email_unknown": "Sorry, but we could not find an account with the provided e-mail adress.",
"anonymous_account_warning": "You are currently logged in using a temporary account which can only be used on this device. If you want to use titra on other devices, please create a regular user with an e-mail address and password below.",
"registration_disabled_warning": "New user registration has been disabled on this titra instance."
"registration_disabled_warning": "New user registration has been disabled on this titra instance.",
"invalid_email": "Invalid e-mail address",
"password_insufficient": "The password must be at least 8 characters long.",
"password_strong": "Password strength: strong",
"password_medium": "Password strength: medium",
"password_weak": "Password strength: weak"
},
"about": {
"documentation": "Documentation",
Expand Down
17 changes: 16 additions & 1 deletion imports/utils/frontend_helpers.js
Expand Up @@ -79,7 +79,21 @@ function validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return re.test(String(email).toLowerCase())
}

function validatePassword(pwd) {
const strongRegex = /^(?=.{14,})(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*\W).*$/g
const mediumRegex = /^(?=.{10,})(((?=.*[A-Z])(?=.*[a-z]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[0-9]))).*$/g
const enoughRegex = /(?=.{8,}).*/g
if (pwd.length === 0) {
return { valid: false, message: t('login.password_insufficient') }
} if (enoughRegex.test(pwd) === false) {
return { valid: false, message: t('login.password_insufficient') }
} if (strongRegex.test(pwd)) {
return { valid: true, message: t('login.password_strong') }
} if (mediumRegex.test(pwd)) {
return { valid: true, message: t('login.password_medium') }
}
return { valid: true, message: t('login.password_weak') }
}
async function emojify(match) {
const emojiImport = await import('node-emoji')
return emojiImport.default.emojify(match, (name) => name)
Expand Down Expand Up @@ -128,6 +142,7 @@ export {
timeInUserUnit,
displayUserAvatar,
validateEmail,
validatePassword,
emojify,
getGlobalSetting,
numberWithUserPrecision,
Expand Down
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e09390c

Please sign in to comment.