1
0
Fork 0

Compare commits

...

10 Commits

21 changed files with 361 additions and 291 deletions

View File

@ -51,7 +51,7 @@
}
},
"devDependencies": {
"electron": "29.1.1",
"electron": "29.1.3",
"electron-builder": "24.13.3"
},
"dependencies": {

View File

@ -769,10 +769,10 @@ electron-publish@24.13.1:
lazy-val "^1.0.5"
mime "^2.5.2"
electron@29.1.1:
version "29.1.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.1.tgz#e9cb11311324e4b43a3e73667cd2b65a30e8fa34"
integrity sha512-cXN15NgCi7MkzGo5/23ZQbii+0UfhmUiDjACunmzcUofYCjF42XhFbL7JZnwgI0qtBCCeJU8qZNZt9lU91gUFw==
electron@29.1.3:
version "29.1.3"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.3.tgz#bd127c6c5bef03ca2cf4595480b8db7e4f111dbe"
integrity sha512-E+ZDRlrVQp4lRxVpK8uTaiHZ8CgjpZEs3gvhFOfbnUGHRHQ6FpPOBZQpQUx84JimOFSaz/KP6Jm2x4TFgoN56A==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"

View File

@ -34,6 +34,22 @@ Claims in turn are assertions containing information about the token bearer, usu
**Scopes** are requested by the client when redirecting the end-user to the Authorization Server for authentication, and indirectly control which claims are included in the resulting tokens.
There's certain default scopes, but its also possible to define custom scopes, which are used by the feature assigning users to Teams automatically.
## Supported and required claims
Vikunja only requires a few claims to be present in the ID token to successfully authenticate the user.
Additional claims can be added though to customize behaviour during user creation.
The following table gives an overview about the claims supported by Vikunja. The scope column lists the scope that should request the claim according to the [OpenID Connect Standard](https://openid.net/specs/openid-connect-core-1_0.html#ScopeClaims). It omits the claims such as `sub` or `issuer` required by the `openid` scope, which must always be present.
| Claim | Type | Scope | Comment |
| ------|------|-------|---------|
| email | required | email | Sets the email address of the user. Taken from the `userinfo` endpoint if not present in ID token. User creation fails if claim not present and userinfo lookup fails. |
| name | optional | profile | Sets the display name of the user. Taken from the `userinfo` endpoint if not present in ID token. |
| preferred_username | optional | profile | Sets the username of the user. Taken from the `userinfo` endpoint if not present in ID token. If this also doesn't contain the claim, use the `nickname` claim from `userinfo` instead. If that one is not available either, the username is auto-generated by Vikunja. |
| vikunja_groups | optional | N/A | Can be used to automatically assign users to teams. See below for a more detailed explanation about the expected format and implementation examples. |
If one of the claims `email`, `name` or `preferred_username` is missing from the ID token, Vikunja will attempt to query the `userinfo` endpoint to obtain the information from there.
## Configuring OIDC Authentication
To achieve authentication via an external provider, it is required to (a) configure a confidential Client on your OAuth 2.0 provider and (b) configure Vikunja to authenticate against this provider.

View File

@ -133,7 +133,7 @@
"@cypress/vue": "6.0.0",
"@faker-js/faker": "8.4.1",
"@histoire/plugin-screenshot": "0.17.8",
"@histoire/plugin-vue": "0.17.12",
"@histoire/plugin-vue": "0.17.13",
"@rushstack/eslint-patch": "1.7.2",
"@tsconfig/node18": "18.2.2",
"@types/codemirror": "5.60.15",
@ -142,7 +142,7 @@
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
"@types/node": "20.11.26",
"@types/node": "20.11.27",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "7.2.0",
@ -150,27 +150,27 @@
"@vitejs/plugin-legacy": "5.3.2",
"@vitejs/plugin-vue": "5.0.4",
"@vue/eslint-config-typescript": "13.0.0",
"@vue/test-utils": "2.4.4",
"@vue/test-utils": "2.4.5",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.18",
"browserslist": "4.23.0",
"caniuse-lite": "1.0.30001597",
"css-has-pseudo": "6.0.2",
"csstype": "3.1.3",
"cypress": "13.6.6",
"cypress": "13.7.0",
"esbuild": "0.20.1",
"eslint": "8.57.0",
"eslint-plugin-vue": "9.23.0",
"happy-dom": "13.8.2",
"happy-dom": "13.8.4",
"histoire": "0.17.9",
"postcss": "8.4.35",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.1",
"postcss-preset-env": "9.5.0",
"postcss-preset-env": "9.5.1",
"rollup": "4.13.0",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.71.1",
"sass": "1.72.0",
"start-server-and-test": "2.0.3",
"typescript": "5.4.2",
"vite": "5.1.6",

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@ import {
getFilterFieldRegexPattern,
LABEL_FIELDS,
} from '@/helpers/filters'
import {useDebounceFn} from '@vueuse/core'
const {
modelValue,
@ -188,6 +189,7 @@ const projectStore = useProjectStore()
function handleFieldInput() {
const cursorPosition = filterInput.value.selectionStart
const textUpToCursor = filterQuery.value.substring(0, cursorPosition)
autocompleteResults.value = []
AUTOCOMPLETE_FIELDS.forEach(field => {
const pattern = new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?$', 'ig')
@ -221,7 +223,7 @@ function handleFieldInput() {
autocompleteResults.value = projectStore.searchProject(search)
}
autocompleteMatchText.value = keyword
autocompleteMatchPosition.value = prefix.length - 1 + keyword.replace(search, '').length
autocompleteMatchPosition.value = match.index + prefix.length - 1 + keyword.replace(search, '').length
}
}
})
@ -236,6 +238,10 @@ function autocompleteSelect(value) {
autocompleteResults.value = []
}
// The blur from the textarea might happen before the replacement after autocomplete select was done.
// That caused listeners to try and replace values earlier, resulting in broken queries.
const blurDebounced = useDebounceFn(() => emit('blur'), 500)
</script>
<template>
@ -264,7 +270,7 @@ function autocompleteSelect(value) {
@input="handleFieldInput"
@focus="onFocusField"
@keydown="onKeydown"
@blur="e => emit('blur', e)"
@blur="blurDebounced"
/>
<div
class="filter-input-highlight"

View File

@ -19,7 +19,7 @@
</Fancycheckbox>
</div>
<FilterInputDocs />
<FilterInputDocs/>
<template
v-if="hasFooter"
@ -48,8 +48,7 @@ export const ALPHABETICAL_SORT = 'title'
</script>
<script setup lang="ts">
import {computed, ref} from 'vue'
import {watchDebounced} from '@vueuse/core'
import {computed, ref, watch} from 'vue'
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
import FilterInput from '@/components/project/partials/FilterInput.vue'
import {useRoute} from 'vue-router'
@ -59,8 +58,8 @@ import {useProjectStore} from '@/stores/projects'
import {FILTER_OPERATORS, transformFilterStringForApi, transformFilterStringFromApi} from '@/helpers/filters'
import FilterInputDocs from '@/components/project/partials/FilterInputDocs.vue'
const {
hasTitle= false,
const {
hasTitle = false,
hasFooter = true,
modelValue,
} = defineProps<{
@ -89,7 +88,7 @@ const params = ref<TaskFilterParams>({
})
// Using watchDebounced to prevent the filter re-triggering itself.
watchDebounced(
watch(
() => modelValue,
(value: TaskFilterParams) => {
const val = {...value}
@ -100,7 +99,7 @@ watchDebounced(
)
params.value = val
},
{immediate: true, debounce: 500, maxWait: 1000},
{immediate: true},
)
const labelStore = useLabelStore()
@ -110,7 +109,10 @@ function change() {
const filter = transformFilterStringForApi(
params.value.filter,
labelTitle => labelStore.filterLabelsByQuery([], labelTitle)[0]?.id || null,
projectTitle => projectStore.searchProject(projectTitle)[0]?.id || null,
projectTitle => {
const found = projectStore.findProjectByExactname(projectTitle)
return found?.id || null
},
)
let s = ''

View File

@ -60,7 +60,7 @@
v-if="showFormSwitch !== null"
class="reminder__close-button"
:shadow="false"
@click="updateDataAndMaybeClose(close)"
@click="updateDataAndMaybeCloseNow(close)"
>
{{ $t('misc.confirm') }}
</x-button>
@ -113,7 +113,7 @@ const presets = computed<TaskReminderModel[]>(() => [
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 7, relativeTo: defaultRelativeTo},
{reminder: null, relativePeriod: -1 * SECONDS_A_DAY * 30, relativeTo: defaultRelativeTo},
])
const reminderDate = ref<Date|null>(null)
const reminderDate = ref<Date | null>(null)
type availableForms = null | 'relative' | 'absolute'
@ -143,16 +143,16 @@ const reminderText = computed(() => {
watch(
() => modelValue,
(newReminder) => {
if(newReminder) {
if (newReminder) {
reminder.value = newReminder
if(newReminder.relativeTo === null) {
if (newReminder.relativeTo === null) {
reminderDate.value = new Date(newReminder.reminder)
}
return
}
reminder.value = new TaskReminderModel()
},
{immediate: true},
@ -182,9 +182,10 @@ function setReminderFromPreset(preset, close) {
close()
}
const updateDataDebounced = useDebounceFn(updateData, 1000)
function updateDataAndMaybeClose(close) {
updateDataDebounced()
const updateDataAndMaybeClose = useDebounceFn(updateDataAndMaybeCloseNow, 500)
function updateDataAndMaybeCloseNow(close) {
updateData()
if (clearAfterUpdate) {
close()
}

View File

@ -107,6 +107,16 @@ describe('Filter Transformation', () => {
expect(transformed).toBe('project = 1')
})
it('should resolve project and labels independently', () => {
const transformed = transformFilterStringForApi(
'project = lorem && labels = ipsum',
multipleDummyResolver,
multipleDummyResolver,
)
expect(transformed).toBe('project = 1 && labels = 2')
})
})
describe('To API', () => {

View File

@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
},
"error": {
"error": "خطأ",
"success": "تم بنجاح",

View File

@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
},
"error": {
"error": "Error",
"success": "Success",

View File

@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
},
"error": {
"error": "Chyba",
"success": "Úspěch",

View File

@ -1095,6 +1095,12 @@
"altFormatLong": "d m Y H:i",
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
},
"error": {
"error": "Fejl",
"success": "Succes",

View File

@ -1096,10 +1096,10 @@
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
"reactedWith": "{user} hat mit {value} reagiert",
"reactedWithAnd": "{users} und {lastUser} haben mit {value} reagiert",
"reactedWithAndMany": "{users} und {num} weitere haben mit {value} reagiert",
"add": "Reaktion hinzufügen"
},
"error": {
"error": "Fehler",

View File

@ -1096,10 +1096,10 @@
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
"reactedWith": "{user} hat mit {value} reagiert",
"reactedWithAnd": "{users} und {lastUser} haben mit {value} reagiert",
"reactedWithAndMany": "{users} und {num} weitere haben mit {value} reagiert",
"add": "Reaktion hinzufügen"
},
"error": {
"error": "Fähler",

View File

@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
},
"error": {
"error": "Fout",
"success": "Succes",

View File

@ -1096,10 +1096,10 @@
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
"reactedWith": "{user} se je odzval z {value}",
"reactedWithAnd": "{users} in {lastUser} sta reagirala z {value}",
"reactedWithAndMany": "{users} in {num} drugih se je odzvalo z {value}",
"add": "Dodaj svoj odziv"
},
"error": {
"error": "Napaka",

View File

@ -1095,6 +1095,12 @@
"altFormatLong": "Y M d H:i",
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
},
"error": {
"error": "错误",
"success": "成功",

View File

@ -1095,6 +1095,12 @@
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"reaction": {
"reactedWith": "{user} reacted with {value}",
"reactedWithAnd": "{users} and {lastUser} reacted with {value}",
"reactedWithAndMany": "{users} and {num} more reacted reacted with {value}",
"add": "Add your reaction"
},
"error": {
"error": "Error",
"success": "Success",

2
go.mod
View File

@ -179,7 +179,7 @@ require (
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.32.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect

4
go.sum
View File

@ -705,8 +705,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=