From 44ee21a71678d8354f125d4ff19030835f4ebeb9 Mon Sep 17 00:00:00 2001 From: Vincent Boutour Date: Sun, 31 Dec 2023 14:54:26 +0100 Subject: [PATCH] chore(deps): Switching to argon2id Signed-off-by: Vincent Boutour --- Makefile | 5 +++-- README.md | 11 +++++------ cmd/fibr/config.go | 2 +- go.mod | 6 +++--- go.sum | 12 ++++++------ infra/web.yaml | 2 +- pkg/crud/crud.go | 20 -------------------- pkg/crud/share.go | 6 +++--- pkg/fibr/fibr.go | 7 ++++--- pkg/fibr/fibr_test.go | 7 ++++--- pkg/mocks/interfaces.go | 15 +++++++++++++++ pkg/mocks/metadata.go | 1 + pkg/mocks/redis_client.go | 1 + pkg/mocks/storage.go | 16 +--------------- pkg/provider/interfaces.go | 1 + pkg/provider/share.go | 24 ++++++++++++++++++++---- pkg/provider/share_test.go | 9 +++++---- pkg/share/crud.go | 26 ++++++++++++++++++++++++++ 18 files changed, 100 insertions(+), 71 deletions(-) diff --git a/Makefile b/Makefile index c04946fd..84c0f0f6 100644 --- a/Makefile +++ b/Makefile @@ -105,9 +105,10 @@ build-web: ## run: Locally run the application, e.g. node index.js, python -m myapp, go run myapp etc ... .PHONY: run run: + go install "github.com/ViBiOh/auth/v2/cmd/argon@latest" $(MAIN_RUNNER) \ -ignorePattern ".git|node_modules" \ - -authUsers "1:`htpasswd -nBb admin admin`" + -authUsers "1:admin:`argon password`" ## config: Create local configuration .PHONY: config @@ -119,4 +120,4 @@ config: config-compose: @printf "DATA_USER_ID=%s\n" "$(shell id -u)" > .env.compose @printf "DATA_DIR=%s\n" "$(shell pwd)" >> .env.compose - @printf "BASIC_USERS=1:%b\n" '$(shell htpasswd -nBb admin password | sed 's|\$$|\$$\$$|g')' >> .env.compose + @printf "BASIC_USERS=1:admin:%s\n" '$(shell argon password | sed 's|\$$|\$$\$$|g')' >> .env.compose diff --git a/README.md b/README.md index 2145d850..2f290009 100644 --- a/README.md +++ b/README.md @@ -134,16 +134,16 @@ Fibr provides [OpenGraph metadatas](https://ogp.me) to have nice preview of link You can start `fibr` with no user, with the `-noAuth` option. Although available, I don't recommend using it in public Internet. Anybody has access to the _root folder_ for viewing, uploading, deleting or sharing content with anybody. -Users are set with the `-authUsers` option and are in the form `[id]:[login]:[bcrypted password]`. +Users are set with the `-authUsers` option and are in the form `[id]:[login]:[argon encoded has]`. - `id` is used to add profile to your user - `login` is the user for Basic Auth prompt -- `bcrypted password` is the password for Basic Auth prompt, [encrypted with `bcrypt`](https://en.wikipedia.org/wiki/Bcrypt) +- `argon encoded hash` is the password for Basic Auth prompt, [encoded hash with `argon2id`](https://en.wikipedia.org/wiki/Argon2) -You can easily encrypt your `login:password` value with [`htpasswd`](https://httpd.apache.org/docs/2.4/programs/htpasswd.html) +You can easily hash your password value with my own [`argon CLI`](https://github.com/ViBiOh/auth/blob/main/cmd/argon/argon.go) or [online](https://argon2.online) ```bash -htpasswd -nBb login password +argon password ``` In order to work, your user **must have** `admin` profile sets with the `-authProfiles` option. @@ -192,7 +192,7 @@ docker run -d \ For prod-ready run with thumbnails generation of image, PDF and videos, _this is the recommended approach_. -You can inspire yourself from the [docker-compose.yaml](docker-compose.yaml) file I personnaly used. Beware of `-authUsers` option: bcrypted passwords contain dollar sign, which `docker-compose` tries to resolve as a shell variable, [you must escape it](https://docs.docker.com/compose/compose-file/compose-file-v2/#variable-substitution). +You can inspire yourself from the [docker-compose.yaml](docker-compose.yaml) file I personnaly used. Beware of `-authUsers` option: hashed passwords contain dollar sign, which `docker-compose` tries to resolve as a shell variable, [you must escape it](https://docs.docker.com/compose/compose-file/compose-file-v2/#variable-substitution). ```bash make config-compose @@ -234,7 +234,6 @@ Usage of fibr: --amqpURI string [amqp] Address in the form amqps?://:@
:/ ${FIBR_AMQP_URI} --authProfiles string [auth] Users profiles in the form 'id:profile1|profile2,id2:profile1' ${FIBR_AUTH_PROFILES} (default "1:admin") --authUsers string [auth] Users credentials in the form 'id:login:password,id2:login2:password2' ${FIBR_AUTH_USERS} - --bcryptDuration string [crud] Wanted bcrypt duration for calculating effective cost ${FIBR_BCRYPT_DURATION} (default "0.25s") --cert string [server] Certificate file ${FIBR_CERT} --chunkUpload [crud] Use chunk upload in browser ${FIBR_CHUNK_UPLOAD} (default false) --csp string [owasp] Content-Security-Policy ${FIBR_CSP} (default "default-src 'self'; base-uri 'self'; script-src 'self' 'httputils-nonce' unpkg.com/webp-hero@0.0.2/dist-cjs/ unpkg.com/leaflet@1.9.4/dist/ unpkg.com/leaflet.markercluster@1.5.1/; style-src 'self' 'httputils-nonce' unpkg.com/leaflet@1.9.4/dist/ unpkg.com/leaflet.markercluster@1.5.1/; img-src 'self' data: a.tile.openstreetmap.org b.tile.openstreetmap.org c.tile.openstreetmap.org") diff --git a/cmd/fibr/config.go b/cmd/fibr/config.go index b2f4f319..d8061e68 100644 --- a/cmd/fibr/config.go +++ b/cmd/fibr/config.go @@ -62,7 +62,7 @@ func newConfig() (configuration, error) { logger: logger.Flags(fs, "logger"), telemetry: telemetry.Flags(fs, "telemetry"), owasp: owasp.Flags(fs, "", flags.NewOverride("FrameOptions", "SAMEORIGIN"), flags.NewOverride("Csp", "default-src 'self'; base-uri 'self'; script-src 'self' 'httputils-nonce' unpkg.com/webp-hero@0.0.2/dist-cjs/ unpkg.com/leaflet@1.9.4/dist/ unpkg.com/leaflet.markercluster@1.5.1/; style-src 'self' 'httputils-nonce' unpkg.com/leaflet@1.9.4/dist/ unpkg.com/leaflet.markercluster@1.5.1/; img-src 'self' data: a.tile.openstreetmap.org b.tile.openstreetmap.org c.tile.openstreetmap.org")), - basic: basicMemory.Flags(fs, "auth", flags.NewOverride("Profiles", "1:admin")), + basic: basicMemory.Flags(fs, "auth", flags.NewOverride("Profiles", []string{"1:admin"})), storage: storage.Flags(fs, ""), crud: crud.Flags(fs, ""), sanitizer: sanitizer.Flags(fs, ""), diff --git a/go.mod b/go.mod index 9461b454..681c90da 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ toolchain go1.21.0 require ( github.com/ViBiOh/ChatPotte v0.4.0 github.com/ViBiOh/absto v1.7.4 - github.com/ViBiOh/auth/v2 v2.17.0 + github.com/ViBiOh/auth/v2 v2.18.0 github.com/ViBiOh/exas v0.7.1 github.com/ViBiOh/flags v1.5.0 - github.com/ViBiOh/httputils/v4 v4.70.1 + github.com/ViBiOh/httputils/v4 v4.70.2 github.com/ViBiOh/vith v0.6.0 github.com/rabbitmq/amqp091-go v1.9.0 github.com/redis/go-redis/v9 v9.3.1 @@ -62,6 +62,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect google.golang.org/grpc v1.60.1 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.32.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 8c9bc34b..cb2f43c7 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,14 @@ github.com/ViBiOh/ChatPotte v0.4.0 h1:SfFOhKqrJShi5E9y2tgpdSSnD6XXeZprWC8LGlrFrJ github.com/ViBiOh/ChatPotte v0.4.0/go.mod h1:jNab+Xjgh+OOmIuzDeH1TywO5nLgdO3uo85/dn6gbX4= github.com/ViBiOh/absto v1.7.4 h1:OSHot3kdWXtM9Brjwth3ZgmlXHq2ZUU3L+PmZVrbaLs= github.com/ViBiOh/absto v1.7.4/go.mod h1:wWKqd0pH9fqxWDZdKhLBnpKFORMdMQJ3tZuu+v3hTrk= -github.com/ViBiOh/auth/v2 v2.17.0 h1:g2XJW2R6kP7Ss39H429O50I7rVF+2dK6u9u8ilAB4eM= -github.com/ViBiOh/auth/v2 v2.17.0/go.mod h1:t85ivqXzje7OtmK9koS0Kzcj5HMTWk1i4ua+ILG5b8k= +github.com/ViBiOh/auth/v2 v2.18.0 h1:nL2+VShyn1ZP2A4EbqbpCKuLsKzXJRYyxDDObDPH120= +github.com/ViBiOh/auth/v2 v2.18.0/go.mod h1:QxUxA32WGj9DwNWc8Yt86F3A6WhkMxrz2UVCxL2e4GM= github.com/ViBiOh/exas v0.7.1 h1:nzh6E1668OFRgoTIJZJa1RybCW64Gd918qi0Z+0Q4WA= github.com/ViBiOh/exas v0.7.1/go.mod h1:MvIWQZXpWgg45Vsde9bMV48Kb0J8frya8vimmrkQ04E= github.com/ViBiOh/flags v1.5.0 h1:nwuFS8tAwtV6rTPpv2pCB+r12WjZYLjluW7yT+SeVpQ= github.com/ViBiOh/flags v1.5.0/go.mod h1:39UMuTnKsIp6walgD8dK99KRCb4DJt9vPtbWehHh1T0= -github.com/ViBiOh/httputils/v4 v4.70.1 h1:Vz0+24CBkjT8HVreX46txu8rbd/2f7ROximutdL0WJM= -github.com/ViBiOh/httputils/v4 v4.70.1/go.mod h1:cyn0nVJoDaHtY1PiiWcNU6rUeN2zxojf4pwQ4sClrjM= +github.com/ViBiOh/httputils/v4 v4.70.2 h1:qpc6q9PadfaiTah03O+hl2IyZK1YRhRDMEiXuywa4pA= +github.com/ViBiOh/httputils/v4 v4.70.2/go.mod h1:b+XnYZgotWxeBKYIrG1Jp1OjOHO51hEND9v0YyQTRt8= github.com/ViBiOh/vith v0.6.0 h1:bdLmwgqUEWi3ivShtAhtM9gOZlWQzvZi2Z5b0BRSB1U= github.com/ViBiOh/vith v0.6.0/go.mod h1:eRqtDU2uB8g85vll4spbnCV93DHyHNAYwXCMIBH1GuQ= github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= @@ -157,8 +157,8 @@ google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= 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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/infra/web.yaml b/infra/web.yaml index 2022596d..9ab0db6b 100644 --- a/infra/web.yaml +++ b/infra/web.yaml @@ -59,7 +59,7 @@ spec: OTEL_SERVICE_NAME: fibr secrets: FIBR_AMQP_URI: AgA9cvt+WxhWbE0BhP+LdNOgUWVNaLr8BBpi1BSVmHByUsUkRwM2rrN6wncgPTfJGW2nRzah+tYfgs2A31d3eNpi1UuG4ndH6IRSxrUJqA5FUXha8NlN5jzpqALFcIaEP0zavkef5U9YmuO3UdUT9o/thHwwJzy+au0DI44qtImIsEtOsVzq4fwBGIt2A+z5QqmeZK0TMcao630QqwLoksY0C0ZR+XomxuaGUCdR7xgiuQnHKeSp9idXoQc5sa84EEuoix+FCXuMwKn26dSytqLgeYpND2Ryj0WeRX6zl4Mf5V65PDBMqPxNvn9l+QxoKbM4HB++6N8oAvfkHQTCIqKH8lsrznpguYt1dYDEZwkMK6ho1Xy/ZVPGdKAELKdt+SYWoaZ9DV4A/sj4SkRFuGvPkoAc8raS/bxyWGbLWtwvCYSr0P99M7zTSDiNzEqy+3zjS0Q6vYE+JPKvAHwMrpAjWzB2hT0en10LPPnjNDNcSRPo118mzpopik752t6Ejx3JzAVU2OI08eSxxoejh/ZabuPocaZIseBRvWxHYoXVbLMgbmuEmvYc0PXm+/FNUsj/d7gs7aEsl5OUDwgpjDUtaq90eW5aUZb6OrGp2sKc3OmXDGq4IWDdqbBV7mzt/sjTvULBe9LwunneZN9OUJvYj5+r5107qaxzOQYmwYx8Qvs7GECbxcXofKg1lfWKUzbnFn9ekbqPegOSTQ5XD/y3LHimvaKtnpKHRXkAW5Si9krBExtljW8WHpTsW2nP9Rhf - FIBR_AUTH_USERS: AgAsKCdUL2LhMJwpK2C9v6IYmYZvhabTQJs797WyCt9TwJR8B2qZzlnDjaLPZLd24vO/i2Y0ACCpN4CEOmWnI4wY03BZcA4RkwYWQMzAllFLiHsJZKlDiIiC/tGTWOZQK3vjxYBTeJv3cDk8l2Quc+rYo/j7frilYblpWe8h8saNUXmxC4wIWJ4PYjeHnMKufSZa29g0jCvSnGW0pHO08rjsAIu6p6eBbL+Qx+M1LUU7x+HZ8U5Irz9sFMJHEfaLM51JZwLyUk6Xwl57d0G/F9UP2BnPQ2QlhlBMeCItxfTW5NVe5teyZH5aF9J8gZTXV0FmjF49MYDhQZKpBv9W69TIxX1+75hfOHYn00w08+bnIRluH8MnbAFDQ9afSPybQP5FxwjOtD5u4mY2u6uVavuxpk5NO6bTi1QHXsLrFXMh2WJZAdRphSKURc5igevG0f03/2FZalaKzlgK6WrNwVnQnT/p8ln+tasbJKK3pfQmc6KG0VfHVdFSD1X8ojGPRgnPEz+VYZTayEEMF1d1selmSjOudcfpyxfuhh3qyuq3tUyxN3E1fqYj/U3cwCx5OuAI3SKOl3yoxPM3ATpzHj/z9pHM/2Z5Dg4ne5aZ5NpnOAzeabiDg/YOQtMm7fqYdQV3eGl5LS7ScLyQR40UU60AZ0ti7VoVd8ZGXE9rb0F0hVVJRcIT5Fe83bP393KdUkTCs9jtMmt5UiBU9rDE2Fmm1ZLLq/k7gx8doCAKoml7BgqQd+0GtW0Vc4oCPNdQrBfDhj3zjs5GF4fksIZTvqWz8vOS+w8= + FIBR_AUTH_USERS: AgAA9QXnVRTRqRTF2N1wwHfJDcQI0qEa9AUnm7exYO28qILdQwzNWh1SjGvpjeM1nENOR/N7r4aMtQGWXbZ9DOqX/moPMKKmJ3NWkCzjDTb5W0rXBF/Wff5m7/bqphY68grFrPObvKORYGhxDmIZBeTk9lakfEKvIcsCglWEkiS7dLwEOe6oL5WxJ47p5JOK/9+keuL9AdechW7Zrod+knLehLVB33i/Jp9jWDvnjkyCNPn0SMqBPZoAqK2NjE+IIRk2/4yqnGrD/4ZOHma0chbe+9IsCW0iQhilorqNq7Ol6PIdhL9vwLwdRdJimDPQplDnSzz4PkFfmerLcdEoaS3Ume0kS2cv4cq3r7E8PBipeWEH9tNDLVi8HOMpdcFaPKF7GUU6gfSN6qfUNGolFnfleCVm2noCxVUGvALK69GC5pz5NTAOfdr8z9Zd03at0nGhVLKXeh3V1ip8T+uqXv0RGdTDGHcJlM9pzuNmuUP9TQANyzDzIiLEPaN/m7go7Z6k6gCnzi2nRLC+TKSONG95pqRDVFDeYy6Ywxw9Lb7CYKSIgzjkX+ZSyioETtWnyyo9NZS1l3qXIh94A+KC/qWfEq4shig+iNSzhhTg5pmQdTJMOYub+CEWXbm1IY3ijK7yPhdPI9ybGyQ+D+mY0ljIEMbDwB/f5NCBRtQzsk/0KY6qp7kxttXIkErOcD/bwEicjSvQQZlkT0dU3v0/cSUJNcg+6YZo9B+8lMXhzkoqPB9Emlv6xSUsPjtNkas+kIOmdK21Dz0p2d95QRStMPOhrzi6zraAd22gQy2jwHO4AgU4JPBP4ioLjyahscs4uk3gfGUrhvTUaXlb0E+Tyc4KawQ= FIBR_REDIS_PASSWORD: AgCnyufAnddFZgVpshL9KFWbkXvtidyO2AlhFpcv3nPiGlqb+QqxC5M2ZaLEzt1vZuaLb3hkDSl1zBy7usmLhPCqsTGCH0JH2tGPkxo8OKa9eFTXzT0EKtoJI7HKHK7D1b5z9IhSEa+sPXvYWVtLjCSvfAXw8/pI+MoDlu1TGmHZvqDpthK1OTu048gVY9NBylpudZ2kQA33Ccfx8tQ0w7RJEswOMScMJ+w7iVuDZrc84NZ0NwdI+lxiTBIP3g2iCh1ENf/2pcB6DqWg5oeFqoYZvK9UXA4qVs/1/EYss7FoODwTnDletITjrfQkx2N9/Z4xUFnW3tY8EITYX5t8uflYg690vfYFBmIoPBpGMHdEr/aicj0YqqtH/7YG4q7ubJqmiGNAjVtUhe9p1jRNppRQQbuLvwKCwxOGMGl9ecUKci9MdR+qM049SSVZJHY1QdZreOoVb4hnrbxQq0s8qyF47IpkwaBh+KEJSepQ7AG2h0wzGc2ln5k7CE5fob0txUzk+Y2nns81maH1nWixCioRDwVMfBjcDWPB3Q5hDt6kHqIpunSEqoe0GVP6SH8ncmcbkJTPwqL44XwTiKcVrTBNuxDbbflTZ0FgXmJP41L6WiScwxN3i6SFTHa3T3rWrktqOHULXabb2UTsW+VvtdX+wx7o35mqaLdI2dkFuB8ZdtWDejX+AZ62hmoK+8tZNsIYWXplaIKYDMojeGJl/JmKsXPbc5QPYYW+8txFImmtyAvXTzY8rF5DSiosZoGeQ97bVZdAHv1rQlrBHAWz+Pg= FIBR_WEBHOOK_SECRET: AgABOeEa3Obbd73SJR32wQ3g5xIlBPCT1jQ3DKYOjzjr6tjz/TmOaVepNtBcX8EU++SQw5tGM6KRJPRyf3bUYKCGnmEsbujJ4OY1MdhaY2iB2FslxMQnx97hvbvYglPltp3hsBuO35r1tzCxXJgWKdWpOSEYQRrGcwzDOGQY71rJX2bMv+lQ4Jr4zXQfsfCW2tCFcpyp5nm6gXmmR0ewlQosdhVEJNXCq1njB42bTVvwLwFKsFIBzLONQYNWLqEa5FVz7EiUkU8ro3ax5So62a8R08bmDAdIzm1TZjdjGywQ23NbGgX6cP8GwAqaenOZ0SE74UXui/ff5HGVef3mGTNYpWlEONfB6qsjemOzR9SihXMysGJMrQ3AnL3VUZJnBUKttxd/iz7hTVHqPcv8rrKESOrODtl94QCIy+7K/u3kZgwlIYj6nn29jo8I/Wpr0Q/1HfUHLIjkPendwr9CTKrmrwKRAl1SkBsoIKGh7WZ8/UFXyQp+fuKdVEzIQ/K4ed/zpJ73MVzX8ATrHa3teBhg69bG/sPgyK7LhE8VbQ57f1gUaZ6VPsBQGRkG/7qsfDSD8MjQhJrAzoIS/THqwN9zWsyzc+Gv/uWMr++kwm2CLeD7yw1SZCpvBB4ebM6GEXH7qtuJQ7W07t/1Rwezhv1JqLKPdl0MAXKJ7VTdZANnvz5ge9KinlMsSli4a8dr1KOtsTW52+C2CiTDkQ2G/RmaQi4VBSpxrioKvVwRkRaWTxy/RO7PPazOy1LvjdjycW4MasPP2PmCyouODR0nUctyADdELxTmUgHIvdUUQ+tDsKkZKo0akn/gTrSBSCFV resources: diff --git a/pkg/crud/crud.go b/pkg/crud/crud.go index 4b60841c..87ecc464 100644 --- a/pkg/crud/crud.go +++ b/pkg/crud/crud.go @@ -3,17 +3,13 @@ package crud import ( "errors" "flag" - "fmt" - "log/slog" "net/http" - "time" absto "github.com/ViBiOh/absto/pkg/model" "github.com/ViBiOh/fibr/pkg/provider" "github.com/ViBiOh/fibr/pkg/search" "github.com/ViBiOh/fibr/pkg/thumbnail" "github.com/ViBiOh/flags" - "github.com/ViBiOh/httputils/v4/pkg/bcrypt" "github.com/ViBiOh/httputils/v4/pkg/renderer" "go.opentelemetry.io/otel/trace" ) @@ -37,12 +33,10 @@ type Service struct { temporaryFolder string renderer *renderer.Service thumbnail thumbnail.Service - bcryptCost int chunkUpload bool } type Config struct { - BcryptDuration string TemporaryFolder string ChunkUpload bool } @@ -50,7 +44,6 @@ type Config struct { func Flags(fs *flag.FlagSet, prefix string) *Config { var config Config - flags.New("BcryptDuration", "Wanted bcrypt duration for calculating effective cost").Prefix(prefix).DocPrefix("crud").StringVar(fs, &config.BcryptDuration, "0.25s", nil) flags.New("ChunkUpload", "Use chunk upload in browser").Prefix(prefix).DocPrefix("crud").BoolVar(fs, &config.ChunkUpload, false, nil) flags.New("TemporaryFolder", "Temporary folder for chunk upload").Prefix(prefix).DocPrefix("crud").StringVar(fs, &config.TemporaryFolder, "/tmp", nil) @@ -76,19 +69,6 @@ func New(config *Config, storageService absto.Storage, filteredStorage absto.Sto service.tracer = tracerProvider.Tracer("crud") } - bcryptDuration, err := time.ParseDuration(config.BcryptDuration) - if err != nil { - return service, fmt.Errorf("parse bcrypt duration: %w", err) - } - - bcryptCost, err := bcrypt.FindBestCost(bcryptDuration) - if err != nil { - slog.Error("find best bcrypt cost", "error", err) - } - slog.Info("Best bcrypt cost computed", "cost", bcryptCost) - - service.bcryptCost = bcryptCost - return service, nil } diff --git a/pkg/crud/share.go b/pkg/crud/share.go index 893d33fa..00b6b41d 100644 --- a/pkg/crud/share.go +++ b/pkg/crud/share.go @@ -8,10 +8,10 @@ import ( "strings" absto "github.com/ViBiOh/absto/pkg/model" + "github.com/ViBiOh/auth/v2/pkg/argon" "github.com/ViBiOh/fibr/pkg/provider" "github.com/ViBiOh/httputils/v4/pkg/model" "github.com/ViBiOh/httputils/v4/pkg/renderer" - "golang.org/x/crypto/bcrypt" ) func (s Service) bestSharePath(pathname string) string { @@ -79,13 +79,13 @@ func (s Service) createShare(w http.ResponseWriter, r *http.Request, request pro password := "" if passwordValue := strings.TrimSpace(r.FormValue("password")); passwordValue != "" { - hash, err := bcrypt.GenerateFromPassword([]byte(passwordValue), s.bcryptCost) + hash, err := argon.GenerateFromPassword(passwordValue) if err != nil { s.error(w, r, request, model.WrapInternal(err)) return } - password = string(hash) + password = hash } ctx := r.Context() diff --git a/pkg/fibr/fibr.go b/pkg/fibr/fibr.go index 9d21ca6c..be8b2d94 100644 --- a/pkg/fibr/fibr.go +++ b/pkg/fibr/fibr.go @@ -1,6 +1,7 @@ package fibr import ( + "context" "errors" "fmt" "log/slog" @@ -53,7 +54,7 @@ func (s Service) parseRequest(r *http.Request) (provider.Request, error) { request.Path = "/" + request.Path } - if err := s.parseShare(&request, r.Header.Get("Authorization")); err != nil { + if err := s.parseShare(r.Context(), &request, r.Header.Get("Authorization")); err != nil { logRequest(r) return request, model.WrapUnauthorized(err) } @@ -101,13 +102,13 @@ func parsePreferences(r *http.Request) provider.Preferences { return provider.ParsePreferences(cookieValue) } -func (s Service) parseShare(request *provider.Request, authorizationHeader string) error { +func (s Service) parseShare(ctx context.Context, request *provider.Request, authorizationHeader string) error { share := s.share.Get(request.Filepath()) if share.IsZero() { return nil } - if err := share.CheckPassword(authorizationHeader); err != nil { + if err := share.CheckPassword(ctx, authorizationHeader, s.share); err != nil { return err } diff --git a/pkg/fibr/fibr_test.go b/pkg/fibr/fibr_test.go index 7306bb41..32f3102f 100644 --- a/pkg/fibr/fibr_test.go +++ b/pkg/fibr/fibr_test.go @@ -1,6 +1,7 @@ package fibr import ( + "context" "encoding/base64" "errors" "fmt" @@ -10,6 +11,7 @@ import ( "strings" "testing" + "github.com/ViBiOh/auth/v2/pkg/argon" "github.com/ViBiOh/auth/v2/pkg/auth" "github.com/ViBiOh/auth/v2/pkg/ident" authModel "github.com/ViBiOh/auth/v2/pkg/model" @@ -17,7 +19,6 @@ import ( "github.com/ViBiOh/fibr/pkg/provider" httpModel "github.com/ViBiOh/httputils/v4/pkg/model" "go.uber.org/mock/gomock" - "golang.org/x/crypto/bcrypt" ) var ( @@ -32,7 +33,7 @@ var ( Path: "/public", } - passwordHash, _ = bcrypt.GenerateFromPassword([]byte("password"), 10) + passwordHash, _ = argon.GenerateFromPassword("password") passwordShare = provider.Share{ ID: "f5d4c3b2a1", @@ -152,7 +153,7 @@ func TestParseShare(t *testing.T) { shareMock.EXPECT().Get(gomock.Any()).Return(provider.Share{}) } - gotErr := tc.instance.parseShare(tc.args.request, tc.args.authorizationHeader) + gotErr := tc.instance.parseShare(context.Background(), tc.args.request, tc.args.authorizationHeader) failed := false diff --git a/pkg/mocks/interfaces.go b/pkg/mocks/interfaces.go index 7b9c172f..71fc4c8b 100644 --- a/pkg/mocks/interfaces.go +++ b/pkg/mocks/interfaces.go @@ -5,6 +5,7 @@ // // mockgen -source interfaces.go -destination ../mocks/interfaces.go -package mocks -mock_names Crud=Crud,Auth=Auth,ShareManager=ShareManager,WebhookManager=WebhookManager // + // Package mocks is a generated GoMock package. package mocks @@ -240,6 +241,20 @@ func (mr *ShareManagerMockRecorder) List() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*ShareManager)(nil).List)) } +// UpdatePassword mocks base method. +func (m *ShareManager) UpdatePassword(arg0 context.Context, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdatePassword", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdatePassword indicates an expected call of UpdatePassword. +func (mr *ShareManagerMockRecorder) UpdatePassword(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdatePassword", reflect.TypeOf((*ShareManager)(nil).UpdatePassword), arg0, arg1, arg2) +} + // WebhookManager is a mock of WebhookManager interface. type WebhookManager struct { ctrl *gomock.Controller diff --git a/pkg/mocks/metadata.go b/pkg/mocks/metadata.go index 72a7bed8..d48044e8 100644 --- a/pkg/mocks/metadata.go +++ b/pkg/mocks/metadata.go @@ -5,6 +5,7 @@ // // mockgen -source metadata.go -destination ../mocks/metadata.go -package mocks -mock_names MetadataManager=MetadataManager // + // Package mocks is a generated GoMock package. package mocks diff --git a/pkg/mocks/redis_client.go b/pkg/mocks/redis_client.go index b50261ff..d2bbd708 100644 --- a/pkg/mocks/redis_client.go +++ b/pkg/mocks/redis_client.go @@ -5,6 +5,7 @@ // // mockgen -destination ../mocks/redis_client.go -package mocks -mock_names Client=RedisClient github.com/ViBiOh/httputils/v4/pkg/redis Client // + // Package mocks is a generated GoMock package. package mocks diff --git a/pkg/mocks/storage.go b/pkg/mocks/storage.go index 13516824..ed07ffeb 100644 --- a/pkg/mocks/storage.go +++ b/pkg/mocks/storage.go @@ -5,6 +5,7 @@ // // mockgen -destination ../mocks/storage.go -package mocks -mock_names Storage=Storage github.com/ViBiOh/absto/pkg/model Storage // + // Package mocks is a generated GoMock package. package mocks @@ -113,21 +114,6 @@ func (mr *StorageMockRecorder) Name() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*Storage)(nil).Name)) } -// OpenFile mocks base method. -func (m *Storage) OpenFile(arg0 context.Context, arg1 string, arg2 int, arg3 fs.FileMode) (model.File, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "OpenFile", arg0, arg1, arg2, arg3) - ret0, _ := ret[0].(model.File) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// OpenFile indicates an expected call of OpenFile. -func (mr *StorageMockRecorder) OpenFile(arg0, arg1, arg2, arg3 any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenFile", reflect.TypeOf((*Storage)(nil).OpenFile), arg0, arg1, arg2, arg3) -} - // Path mocks base method. func (m *Storage) Path(arg0 string) string { m.ctrl.T.Helper() diff --git a/pkg/provider/interfaces.go b/pkg/provider/interfaces.go index e26a45f0..f6b28a27 100644 --- a/pkg/provider/interfaces.go +++ b/pkg/provider/interfaces.go @@ -32,6 +32,7 @@ type ShareManager interface { List() []Share Get(string) Share Create(context.Context, string, bool, bool, string, bool, time.Duration) (string, error) + UpdatePassword(context.Context, string, string) error Delete(context.Context, string) error } diff --git a/pkg/provider/share.go b/pkg/provider/share.go index cae6a1ce..dd363e86 100644 --- a/pkg/provider/share.go +++ b/pkg/provider/share.go @@ -1,12 +1,15 @@ package provider import ( + "context" "encoding/base64" "errors" + "log/slog" "strconv" "strings" "time" + "github.com/ViBiOh/auth/v2/pkg/argon" "golang.org/x/crypto/bcrypt" ) @@ -39,7 +42,7 @@ func (s Share) IsZero() bool { return len(s.ID) == 0 } -func (s Share) CheckPassword(authorizationHeader string) error { +func (s Share) CheckPassword(ctx context.Context, authorizationHeader string, shareApp ShareManager) error { if s.Password == "" { return nil } @@ -61,11 +64,24 @@ func (s Share) CheckPassword(authorizationHeader string) error { } password := dataStr[sepIndex+1:] - if err = bcrypt.CompareHashAndPassword([]byte(s.Password), []byte(password)); err != nil { - return errors.New("invalid credentials") + + switch { + case strings.HasPrefix(string(s.Password), "$argon2id"): + if argon.CompareHashAndPassword(s.Password, password) == nil { + return nil + } + + default: + if bcrypt.CompareHashAndPassword([]byte(s.Password), []byte(password)) == nil { + if err := shareApp.UpdatePassword(ctx, s.ID, password); err != nil { + slog.ErrorContext(ctx, "update password", "error", err) + } + + return nil + } } - return nil + return errors.New("invalid credentials") } func (s Share) IsExpired(now time.Time) bool { diff --git a/pkg/provider/share_test.go b/pkg/provider/share_test.go index 69e534f8..2de11ba5 100644 --- a/pkg/provider/share_test.go +++ b/pkg/provider/share_test.go @@ -1,18 +1,19 @@ package provider import ( + "context" "encoding/base64" "errors" "fmt" "testing" - "golang.org/x/crypto/bcrypt" + "github.com/ViBiOh/auth/v2/pkg/argon" ) func TestCheckPassword(t *testing.T) { - password, err := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost) + password, err := argon.GenerateFromPassword("test") if err != nil { - t.Errorf("create bcrypted password: %s", err) + t.Errorf("create argon password: %s", err) } cases := map[string]struct { @@ -71,7 +72,7 @@ func TestCheckPassword(t *testing.T) { for intention, tc := range cases { t.Run(intention, func(t *testing.T) { - err := tc.share.CheckPassword(tc.header) + err := tc.share.CheckPassword(context.Background(), tc.header, nil) failed := false diff --git a/pkg/share/crud.go b/pkg/share/crud.go index 3ed98c3f..c2c41d1a 100644 --- a/pkg/share/crud.go +++ b/pkg/share/crud.go @@ -7,6 +7,7 @@ import ( "sort" "time" + "github.com/ViBiOh/auth/v2/pkg/argon" "github.com/ViBiOh/fibr/pkg/exclusive" "github.com/ViBiOh/fibr/pkg/provider" ) @@ -78,6 +79,31 @@ func (s *Service) Create(ctx context.Context, filepath string, edit, story bool, return id, err } +func (s *Service) UpdatePassword(ctx context.Context, id, password string) error { + _, err := s.Exclusive(ctx, id, exclusive.Duration, func(_ context.Context) error { + hashed, err := argon.GenerateFromPassword(password) + if err != nil { + return fmt.Errorf("hash password: %w", err) + } + + share := s.shares[id] + share.Password = hashed + s.shares[id] = share + + if err := provider.SaveJSON(ctx, s.storage, shareFilename, s.shares); err != nil { + return fmt.Errorf("save shares: %w", err) + } + + if err := s.redisClient.PublishJSON(ctx, s.pubsubChannel, provider.Share{ID: id}); err != nil { + return fmt.Errorf("publish share deletion: %w", err) + } + + return nil + }) + + return err +} + func (s *Service) Delete(ctx context.Context, id string) error { _, err := s.Exclusive(ctx, id, exclusive.Duration, func(_ context.Context) error { return s.delete(ctx, id)