diff --git a/.github/workflows/cargo.yml b/.github/workflows/cargo.yml new file mode 100644 index 0000000..401b7c4 --- /dev/null +++ b/.github/workflows/cargo.yml @@ -0,0 +1,51 @@ +name: Cargo CI + +on: + push: + pull_request: + +env: + CARGO_TERM_COLOR: always + +jobs: + fmt: + name: Cargo fmt + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - stable + + steps: + - uses: actions/checkout@v4 + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: rustup component add --toolchain ${{ matrix.toolchain }} rustfmt + - run: cd server && cargo fmt --all --check + + build: + name: Cargo build + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - stable + - nightly + target: + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + + steps: + - uses: actions/checkout@v4 + - name: Cache + uses: actions/cache@v2 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + server/target + key: ${{ runner.os }}-${{ matrix.toolchain }}-${{ matrix.target }}-cargo-${{ hashFiles('server/**/Cargo.lock') }} + + - run: sudo apt install -y musl-tools + - run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }} + - run: rustup target add --toolchain ${{ matrix.toolchain }} ${{ matrix.target }} + - run: cd server && cargo build --target ${{ matrix.target }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1f30c98 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,48 @@ +name: Release + +on: + push: + tags: + - '*' + +env: + CARGO_TERM_COLOR: always + DOCK_REG: registry.mrfriendly.uk + +jobs: + server: + name: Server build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: make build-server + - run: docker tag $DOCK_REG/wilford-server:latest $DOCK_REG/wilford-server:${{ github.ref_name }} + - run: docker push $DOCK_REG/wilford-server:latest + - run: docker push $DOCK_REG/wilford-server:${{ github.ref_name }} + + ui: + name: UI build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '21' + - run: (cd ui; yarn install) + + - run: make build-ui + - run: docker tag $DOCK_REG/wilford-ui:latest $DOCK_REG/wilford-ui:${{ github.ref_name }} + - run: docker push $DOCK_REG/wilford-ui:latest + - run: docker push $DOCK_REG/wilford-ui:${{ github.ref_name }} + + docs: + name: Docs build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: cargo install mdbook --locked + + - run: make build-docs + - run: docker tag $DOCK_REG/wilford-docs:latest $DOCK_REG/wilford-docs:${{ github.ref_name }} + - run: docker push $DOCK_REG/wilford-docs:latest + - run: docker push $DOCK_REG/wilford-docs:${{ github.ref_name }} \ No newline at end of file diff --git a/Makefile b/Makefile index 3229f3f..9163a28 100644 --- a/Makefile +++ b/Makefile @@ -11,8 +11,17 @@ help: echo "- upload-docs : Build and upload the docs Docker image" echo "- upload-ui : Build and upload the ui Docker image" +test_oidc_key.pem: + openssl genrsa -out ./test_oidc_key.pem 4096 + +test_oidc_key.pem.pub: test_oidc_key.pem + openssl rsa -in ./test_oidc_key.pem -pubout -outform PEM -out ./test_oidc_key.pem.pub + +config.json: sample_config.json + cp sample_config.json config.json + .PHONY: up -up: +up: test_oidc_key.pem test_oidc_key.pem.pub config.json docker compose up -d echo "Wilford UI available at http://localhost:2522" echo "Wilford Docs available at http://localhost:2523" @@ -44,7 +53,4 @@ build-docs: .PHONY: build-ui build-ui: - # Patch for production - sed -i "s|createWebHistory('/')|createWebHistory('/wilford')|" ui/src/router/index.ts - - docker build -t registry.mrfriendly.uk/wilford-ui ui/ \ No newline at end of file + docker build -t registry.mrfriendly.uk/wilford-ui ui/ diff --git a/README.md b/README.md index 195d2c7..ef455a7 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,14 @@ Your applications will then authenticate with Wilford, and your users can continue using their EspoCRM login credentials. ## Development -- Copy the sample config to `config.json` -- Start all docker containers with docker-compose: -``` -docker compose up -d +- Start everything with +```bash +make up ``` +This will: +- Create an OIDC signing key if it doesn't exist +- Copy `sample_config.json` to `config.json` +- Start all containers The following services will be available: - The backend, on port [2521](http://localhost:2512) @@ -32,7 +35,20 @@ After starting, you should configure an API-client in EspoCRM: 10. Select the role you just created under `Roles` 11. Set `Authentication method` to `HMAC` and select `Save` 12. Copy the `API Key` and `Secret Key` to `config.json` -13. Hit Ctrl+C and run `docker compose up` again. +13. Restart Wilford +```bash +docker-compose down +make up +``` + +## Generate OIDC Key +```bash +# Private key +openssl genrsa -out ./oidc.pem 4096 + +# Public key +openssl rsa -in ./oidc.pem -pubout -outform PEM -out ./oidc.pem.pub +``` # License MIT or Apache-2.0, at your option diff --git a/docker-compose.yml b/docker-compose.yml index a96dbca..a46dd20 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,11 +25,14 @@ services: context: server dockerfile: Dockerfile ports: - - "2521:8080" + - "2521:2521" environment: - "CONFIG_PATH=/config.json" + - "RUST_LOG=DEBUG" volumes: - "./config.json:/config.json" + - "./test_oidc_key.pem:/test_oidc_key.pem" + - "./test_oidc_key.pem.pub:/test_oidc_key.pem.pub" depends_on: - "mariadb-wilford" @@ -63,3 +66,11 @@ services: - "./tmp/espocrm:/var/www/html" depends_on: - mariadb-espocrm + + nginx: + image: nginx + network_mode: "host" + volumes: + - "./localhost.pem:/etc/ssl/certs/ssl-cert-snakeoil.pem" + - "./localhost-key.pem:/etc/ssl/private/ssl-cert-snakeoil.key" + - "./nginx.conf:/etc/nginx/conf.d/default.conf:ro" \ No newline at end of file diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 28f0e18..b79c4d5 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -1,5 +1,8 @@ # Summary - [Introduction](introduction.md) +- [Deploying](./deploy/index.md) + - [Configuration](./deploy/configuration.md) + - [OAuth2 Proxy](./deploy/oauth2_proxy.md) - [OAuth2](oauth2/index.md) - [Authorization](oauth2/authorization.md) - [API](api/index.md) diff --git a/docs/src/api/clients/internal.md b/docs/src/api/clients/internal.md index 5324d73..99b64b2 100644 --- a/docs/src/api/clients/internal.md +++ b/docs/src/api/clients/internal.md @@ -9,7 +9,6 @@ This endpoint provides information about the OAuth2 client { "name": "", "client_id": "", - "client_secret": "", "redirect_uri": "" } ``` \ No newline at end of file diff --git a/docs/src/api/user/index.md b/docs/src/api/user/index.md index dfe60da..b44bd50 100644 --- a/docs/src/api/user/index.md +++ b/docs/src/api/user/index.md @@ -1,2 +1,2 @@ # User -User info & user managment \ No newline at end of file +User info & user management \ No newline at end of file diff --git a/docs/src/deploy/configuration.md b/docs/src/deploy/configuration.md new file mode 100644 index 0000000..8980f78 --- /dev/null +++ b/docs/src/deploy/configuration.md @@ -0,0 +1,19 @@ +# Configuration + + +## Default config file +```json +{{#include ../../../sample_config.json}} +``` + +## Environmental variables +``` +CONFIG_PATH= +``` + +## Available options +The following Rust structs define the layout of the configuration. +An example of how this translates to JSON can be found in the [sample config](#default-config-file) +```rust,noplayground +{{#include ../../../server/wilford/src/config.rs:config}} +``` \ No newline at end of file diff --git a/docs/src/deploy/index.md b/docs/src/deploy/index.md new file mode 100644 index 0000000..5ccaf93 --- /dev/null +++ b/docs/src/deploy/index.md @@ -0,0 +1 @@ +# Deployment Documentation \ No newline at end of file diff --git a/docs/src/deploy/oauth2_proxy.md b/docs/src/deploy/oauth2_proxy.md new file mode 100644 index 0000000..b26b1c7 --- /dev/null +++ b/docs/src/deploy/oauth2_proxy.md @@ -0,0 +1,96 @@ +# OAuth2 Proxy + +Wilford supports running with [oauth2-proxy](https://oauth2-proxy.github.io/oauth2-proxy/). +Using oaut2-proxy together with Wilford and nginx, you can protect static resources without needing to modify them. + +## OAuth2 Config +Sample docker-compose file for running oauth2-proxy. +Replace the `CLIENT_ID` and `CLIENT_SECRET` with the ID and Secret generated by Wilford. Use `REDIRECT_URL` as the Redirect URL in Wilford. +The `COOKIE_SECRET` should be a securely generated random string. +```yml +version: '3.2' +services: + oauth2_proxy: + image: quay.io/oauth2-proxy/oauth2-proxy + environment: + - "OAUTH2_PROXY_COOKIE_SECRET=VsZqXqHQzwdPUcEUDgNxmQvTRZ46DtlQr8q-HtomkL8=" + - "OAUTH2_PROXY_COOKIE_SECURE=true" + - "OAUTH2_PROXY_COOKIE_DOMAIN=localhost" + - "OAUTH2_PROXY_CLIENT_ID=NuWrxroZbOuhBL2ufHx9zj0qKT6XXQRg" + - "OAUTH2_PROXY_CLIENT_SECRET=vwn0MqNbD9qAnvCbGns9sNtikWC7eTM2V7DIz85vcimtxm12" + - "OAUTH2_PROXY_OIDC_ISSUER_URL=https://localhost:8443" + - "OAUTH2_PROXY_REDIRECT_URL=https://localhost:8443/oauth2/callback" + - "OAUTH2_PROXY_PROVIDER=oidc" + - "OAUTH2_PROXY_EMAIL_DOMAINS=*" + - "OAUTH2_PROXY_OIDC_EMAIL_CLAIM=sub_email" + - "OAUTH2_PROXY_PROVIDER_DISPLAY_NAME=Koala" + - "OAUTH2_PROXY_CUSTOM_SIGN_IN_LOGO=-" + - "OAUTH2_PROXY_BANNER=" + - "OAUTH2_PROXY_FOOTER=-" + network_mode: "host" +``` + +## Nginx auth_directive +Using this setup, you can use the nginx auth directive very easily: +```conf + location /secure { + auth_request /oauth2/auth; + error_page 401 =403 /oauth2/sign_in; + proxy_pass http://my-secure-backend; + } +``` + +## Localhost +The JWKS specification requires it be served over https. When working locally, this can be a bit of a pain. + +### Generate certificates for localhost +Install the required tools: +```bash +sudo apt install -y libnss3-tools mkcert +``` +Generate a CA cert: +```bash +mkcert --install +``` + +Generate SSL certificate for `localhost`, from the repository root: +```bash +mkcert localhost +``` + +### Oauth2-proxy +Add the following to the docker-compose file: +```yml +volumes: + - "/usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro" + - "/etc/ssl/certs:/etc/ssl/certs:ro" +``` + +### nginx +Use the following block for Wilford: +```conf +server { + listen 8443 ssl default_server; + server_name _; + + ssl_certificate /etc/ssl/certs/ssl-cert-localhost.pem; + ssl_certificate_key /etc/ssl/private/ssl-cert-localhost.key; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://localhost:2521; + } +} +``` +docker-compose: +```yml + nginx: + image: nginx + network_mode: "host" + volumes: + - "./localhost.pem:/etc/ssl/certs/ssl-cert-localhost.pem" + - "./localhost-key.pem:/etc/ssl/private/ssl-cert-localhost.key" + - "./nginx.conf:/etc/nginx/conf.d/default.conf:ro" +``` \ No newline at end of file diff --git a/docs/src/introduction.md b/docs/src/introduction.md index c88fcb4..9479eb2 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -3,7 +3,13 @@ Wilford is a OAuth2 Provider using EspoCRM as its credentials provider. ## Sources -Wilford's implementation of OAuth2 is derived from the following documents: +Wilford's implementation of OAuth2 and OpenID Connect is derived from the following documents: - [RFC6749](https://datatracker.ietf.org/doc/html/rfc6749) - [RFC6750](https://datatracker.ietf.org/doc/html/rfc6750) -- [RFC7662](https://datatracker.ietf.org/doc/html/rfc7662) \ No newline at end of file +- [RFC7662](https://datatracker.ietf.org/doc/html/rfc7662) +- [OpenID Connect Core 1.0](https://openid.net/specs/openid-connect-core-1_0.html) + +## TODO +Not everything is implemented 100%. I'd like to add support for: +- [A.4](https://openid.net/specs/openid-connect-core-1_0.html#code-id_tokenExample) (`response_type=code id_token`). +At the moment only `response_type=id_token token` is supported. \ No newline at end of file diff --git a/docs/src/oauth2/authorization.md b/docs/src/oauth2/authorization.md index a3d38ed..cc2da46 100644 --- a/docs/src/oauth2/authorization.md +++ b/docs/src/oauth2/authorization.md @@ -5,15 +5,17 @@ Logging in using Wilford OAuth2 authentication. The following steps give a global outline of the OAuth2 login process 1. Your application redirects the resource owner to Wilford (Authorization step) 2. Resource owner logs in with Wilford -2. Resourec owner is redirected to your client -3. Your client exchanges the authorization code for an access token and refresh token +3. Resource owner is redirected to your client +4. Your client exchanges the authorization code for an access token and refresh token ## Authorization Supported OAuth2 flows: 1. Authorization Code 2. Implicit -### Authorization Code +Furthermore, Wilford supports the [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) flow. + +### Authorization Code Flow Redirect the resource owner to `/api/oauth/authorize` with the following query parameters (`application/x-www-form-urlencoded`): ``` response_type: code @@ -22,12 +24,13 @@ redirect_uri: scope: state: ``` -The state parameter will be given back to your after the authorization, unmodified. + +The state parameter will be given back to you after the authorization, unmodified. #### Success 1. The resource owner will be redirected to Wilford's login page, where they must log in using their EspoCRM credentials. 2. The resource owner will be asked to grant your client access -3. The resource owner will be redirected to your `redirect_uri`. The `scope` parameter will contain the the scopes that were authorized. The `state` parameter contains the `state` you provided earlier (Optional). The authorization code is provided in the `code` query parameter. +3. The resource owner will be redirected to your `redirect_uri`. The `scope` parameter will contain the scopes that were authorized. The `state` parameter contains the `state` you provided earlier (Optional). The authorization code is provided in the `code` query parameter. You should now exchange the `code` for an access- and refresh token using the Token endpoint. @@ -35,7 +38,7 @@ You should now exchange the `code` for an access- and refresh token using the To 1. The resource owner will be redirected to your `redirect_uri`. The `error` query parameter will contain the error. The `error` parameter will contain a value as described per [RFC6749 Section 4.1.2.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1) -### Implicit +### Implicit Flow Redirect the resource owner to `/api/oauth/authorize` with the following query parameters (`application/x-www-form-urlencoded`): ``` response_type: token @@ -44,7 +47,7 @@ redirect_uri: scope: state: ``` -The state parameter will be given back to your after the authorization, unmodified. +The state parameter will be given back to you after the authorization, unmodified. #### Success 1. The resource owner will be redirected to Wilford's login page, where they must log in using their EspoCRM credentials. @@ -57,10 +60,33 @@ This is the end of the implicit flow. 1. The resource owner will be redirected to your `redirect_uri`. The `error` query parameter will contain the error. The `error` parameter will contain a value as described per [RFC6749 Section 4.1.2.1](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1) +### OpenID Flow +Redirect the resource owner to `/api/oauth/authorize` with the following query parameters (`application/x-www-form-urlencoded`): +``` +response_type: id_token token +client_id: +redirect_uri: +scope: +state: +nonce: +``` + +The state parameter will be given back to you after the authorization, unmodified. The nonce +parameter will be passed to the JWT token unmodified if OpenID is used. + +#### Success +1. The resource owner will be redirected to Wilford's login page, where they must log in using their EspoCRM credentials. +2. The resource owner will be asked to grant your client access +3. The resource owner will be redirected to your `redirect_uri`. +The `scope` parameter will contain the scopes that were authorized. +The `state` parameter contains the `state` you provided earlier (Optional). +The `id_token` parameter contains the JWT. +The `access_token` parameter contains the OAuth2 access token. + ## Token exchange >Note: This endpoint is only useful if you used the Authorization Code flows -If the previous step went successfull, your client can now exchange the authorization grant for an access- and refresh token. The `code` is contained in the query parameters, as described in the previous step. +If the previous step went successful, your client can now exchange the authorization grant for an access- and refresh token. The `code` is contained in the query parameters, as described in the previous step. Your client should send a `POST` request to `/api/oauth/token` with the following body (`application/x-www-form-urlencoded`): ``` @@ -78,7 +104,7 @@ client_secret: "token_type": "bearer", "expires_in": 3600, "refresh_token": "", - "scope": "", + "scope": "" } ``` Your application can now use the `access_token` to communicate with resource servers. diff --git a/docs/src/oauth2/introspect.md b/docs/src/oauth2/introspect.md index 5c90774..41cd8a6 100644 --- a/docs/src/oauth2/introspect.md +++ b/docs/src/oauth2/introspect.md @@ -1,4 +1,4 @@ -# Instrospect +# Introspect Token introspection endpoint. See also: [RFC7662](https://datatracker.ietf.org/doc/html/rfc7662) diff --git a/localhost-key.pem b/localhost-key.pem new file mode 100644 index 0000000..e616212 --- /dev/null +++ b/localhost-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDsNbwWcAH2Dss+ +GPSLZkO1gYb8SOFTbjSw/pXCVudZNiLbCPNWrALSLpgwLiXJQyQLwG40lL4jOOl1 ++p4nKGYl/6zRpPLolvngQH+GVbUJbODiYLY/MqjVdMT3PX97qIKGpqb2zzcPHm2E +ntlTfh7RIHzlvej9zPpccG62a4w9C67oj7Bh+E274HQY96pPSw3eFcictXekPoxS +lIdVnOh+3uP4TI/MnKDrcn346FJqnQZD9AnwQ5B7x4oB+d9BrmMyzWyQqF1jNxbV +CTzoTeKoyqsdJzCuecLeCHN+soSuK2aM9d2Wx5Pb16VUYD8XKMjEYg72tYNrGx7a +whMQ189rAgMBAAECggEAWUmSmJSsWRuMfiOmxM7aR1D3+oN+ETB2YHVLnNOGzfUl +xdAjU57fzh1oz8WR6PslNAAAaIXVPbE0prEeeUTPIAv+gpysaXkwaTFYQypArZhn +hYrzOP5oTY+/KIopl0/CTy3NrTv03xUsZtY45lOlSH3UWG+qE84Y0Tp6zx/mOehn +qVyWC+klhbcYbJDh2bRcddJbYv7TbtHMLKpvIiM2As5mYeDJChDX1oBPRirabALs +gxfvY+NWw/DmL+Nn7QRL6Il6Mr5U5GGLYiRyRlnQHRGPaZ2OjNiRmmxsVYkKcFQ2 +7XWkNl4kKKgA+plqryGKl47blRG0p2fNhADjTax9iQKBgQDt0L9QFEE5YlFWS9/o +wOg2DKKTkDUUnBBKlw0nbx58ygLb0VZaPCoWp/Mukxd7Lry6PBdSMXsIPIZnY+BP +52A3o1a0sy/ssmNL97CJmTyQbDNKoXWzY8W/Ra0m0R6nbnV/CoFhbHmQoZnB4TeP +41t/EIC+1u8CBUIoZOpo4Y+EhwKBgQD+RY8tAvqYpR0XaxgKhxPxSgbtXAH/6dvd +fVVhsUqtfdE4gFZbJmHXdC2vf1bKyHQ7D/g4rqpUe2aauDUSD57WObBfl1w7i/uR +3dAyUFEwme4KsJz5nS0fNQxZ/6+0MFEVtywe3w96Jr15RhltwKZSZxhZehxcnlvH +jpvESDr6/QKBgEFU23nQVqrBC789UOHMPP68Md1//FURGpijLoXqzOFTTb29oI9h +f96BfRkKZ6T7jfVLlMyLs1Tr67Bzi6fn1FL0mFlD8KKBzy2LegATDMRQNTcHbCJA +Ao8tQQgs4tL0UWr5I9nzxuGow2izymPI/dXGXtgOi9JuR2J5drwhWx/5AoGABdPW +SjPNRn5SQl0j+enKnTcTHZGEQjc74MGkmU6U5ZECoIbgc8pXZ7az7Ve/x3n8n/Xn +vHTUVodVfKpIHRfajhJYZnhzlrHInDk3MlAA7Fo6yGfv0RC3HgX7OHzRrBGHajX+ +ft6h3izRHtxqbMeDiFPwjOxthfnjJJmyHDeDkokCgYEA68cGwARZoX1lyzcXGbfb +TwOxJc79TU+zhdQVH/aTI6hzMeTdoc2qT+yPU8qOgJkVoCBM/TaRrIhrpGy+Tf1o +P4Gw1qcJWNMRdg1ssdyKCueCIx+M7DdNPA578UhD5RgdiCUmu0ufLWdC5ZPVxv8c +fFo8K4Bc9wBn4W3UBsnBROc= +-----END PRIVATE KEY----- diff --git a/localhost.pem b/localhost.pem new file mode 100644 index 0000000..927aec2 --- /dev/null +++ b/localhost.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUjCCArqgAwIBAgIQAKAUPbumOzgi7b+DtrstqDANBgkqhkiG9w0BAQsFADCB +jTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTEwLwYDVQQLDCh0b2Jp +YXNAdG9iaWFzLWRlc2t0b3AgKFRvYmlhcyBkZSBCcnVpam4pMTgwNgYDVQQDDC9t +a2NlcnQgdG9iaWFzQHRvYmlhcy1kZXNrdG9wIChUb2JpYXMgZGUgQnJ1aWpuKTAe +Fw0yNDA2MzAxMzQ3MzBaFw0yNjA5MzAxMzQ3MzBaMFwxJzAlBgNVBAoTHm1rY2Vy +dCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTExMC8GA1UECwwodG9iaWFzQHRvYmlh +cy1kZXNrdG9wIChUb2JpYXMgZGUgQnJ1aWpuKTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOw1vBZwAfYOyz4Y9ItmQ7WBhvxI4VNuNLD+lcJW51k2ItsI +81asAtIumDAuJclDJAvAbjSUviM46XX6nicoZiX/rNGk8uiW+eBAf4ZVtQls4OJg +tj8yqNV0xPc9f3uogoampvbPNw8ebYSe2VN+HtEgfOW96P3M+lxwbrZrjD0LruiP +sGH4TbvgdBj3qk9LDd4VyJy1d6Q+jFKUh1Wc6H7e4/hMj8ycoOtyffjoUmqdBkP0 +CfBDkHvHigH530GuYzLNbJCoXWM3FtUJPOhN4qjKqx0nMK55wt4Ic36yhK4rZoz1 +3ZbHk9vXpVRgPxcoyMRiDva1g2sbHtrCExDXz2sCAwEAAaNeMFwwDgYDVR0PAQH/ +BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFNFs6uYdWwV2 +bmxT5oolJDjqSqCcMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsF +AAOCAYEAKMi6fznFFhcxHKl94lIW8WBuw8SwbtRCRmugl1UFjT9zHYijLdwSTcPs +1FTIBSskK8xZFWI4b+UQJDe6jZEhuYUHRownx9OYznPy2daBK2Mnh3u7Ni07d7R5 +EomqHurTxVTxC0+hnV487zLpXDZr3Xz0Q9YORuHOYj2ayMUOrahp6aR2ppBWFTAB +bOgioBj9qcavx0YWC5P3WzljG/+G1x2KQQ5Q1zrxy2E4bxwAWpxXjIMvTWjDq/NQ +wD6NCAPbRVvBEM1rgBJ3chKAQqp5VQ3oebxPXyQDoUG/WCTVgBfmO9t6mUT7eONT +pbKRxSP6CGnIdhPFCscODs9CffcfNBznUFj886q2+3vwOnFa1uZCLdQ2i35wurxl +PffKLJS+oJyUpP9qXPY7BNzmodw6hQwLhk4Mv3IGUWJcgfuHkIlsLYkUv41G4BMJ +wHGKflYVCTU+GVeT/M4zP/W5yLM0k1h56s+qgd5d8vlXIvIBnEmV9Y9jcgCT8+Wz +kYkapOB2 +-----END CERTIFICATE----- diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..063689d --- /dev/null +++ b/nginx.conf @@ -0,0 +1,39 @@ +server { + listen 8443 ssl default_server; + server_name _; + + ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem; + ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key; + + location /oauth2/ { + proxy_pass http://127.0.0.1:4180; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Auth-Request-Redirect $request_uri; + # or, if you are handling multiple domains: + # proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri; + } + location = /oauth2/auth { + proxy_pass http://127.0.0.1:4180; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Uri $request_uri; + # nginx auth_request includes headers but not body + proxy_set_header Content-Length ""; + proxy_pass_request_body off; + } + + location /docs { + auth_request /oauth2/auth; + error_page 401 =403 /oauth2/sign_in; + proxy_pass https://google.com; + } + + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://localhost:2521; + } +} \ No newline at end of file diff --git a/oauth2_proxy.docker-compose.yml b/oauth2_proxy.docker-compose.yml new file mode 100644 index 0000000..148b0ed --- /dev/null +++ b/oauth2_proxy.docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.2' +services: + oauth2_proxy: + image: quay.io/oauth2-proxy/oauth2-proxy + volumes: + - "/usr/local/share/ca-certificates:/usr/local/share/ca-certificates:ro" + - "/etc/ssl/certs:/etc/ssl/certs:ro" + environment: + - "OAUTH2_PROXY_COOKIE_SECRET=VsZqXqHQzwdPUcEUDgNxmQvTRZ46DtlQr8q-HtomkL8=" + - "OAUTH2_PROXY_COOKIE_SECURE=true" + - "OAUTH2_PROXY_COOKIE_DOMAIN=localhost" + - "OAUTH2_PROXY_CLIENT_ID=NuWrxroZbOuhBL2ufHx9zj0qKT6XXQRg" + - "OAUTH2_PROXY_CLIENT_SECRET=vwn0MqNbD9qAnvCbGns9sNtikWC7eTM2V7DIz85vcimtxm12" + - "OAUTH2_PROXY_OIDC_ISSUER_URL=https://localhost:8443" + - "OAUTH2_PROXY_REDIRECT_URL=https://localhost:8443/oauth2/callback" + - "OAUTH2_PROXY_PROVIDER=oidc" + - "OAUTH2_PROXY_EMAIL_DOMAINS=*" + - "OAUTH2_PROXY_OIDC_EMAIL_CLAIM=sub_email" + - "OAUTH2_PROXY_PROVIDER_DISPLAY_NAME=Koala" + - "OAUTH2_PROXY_CUSTOM_SIGN_IN_LOGO=-" + - "OAUTH2_PROXY_BANNER=" + - "OAUTH2_PROXY_FOOTER=-" + network_mode: "host" diff --git a/sample_config.json b/sample_config.json index f147ca7..bac7abf 100644 --- a/sample_config.json +++ b/sample_config.json @@ -1,19 +1,25 @@ { - "http": { - "ui_login_path": "http://localhost:2522/login" - }, - "database": { - "user": "wilford", - "password": "wilford", - "host": "mariadb-wilford", - "database": "wilford" - }, - "espo": { - "host": "http://espocrm", - "api_key": "", - "secret_key": "" - }, - "default_client": { - "redirect_uri": "http://localhost:2522/login-ok" - } - } \ No newline at end of file + "http": { + "ui_login_path": "http://localhost:2522/login", + "authorization_endpoint": "http://localhost:2521/api/oauth/authorize", + "token_endpoint": "http://localhost:2521/api/oauth/token", + "jwks_uri_endpoint": "https://localhost:2521/.well-known/jwks.json" + }, + "database": { + "user": "wilford", + "password": "wilford", + "host": "mariadb-wilford", + "database": "wilford" + }, + "espo": { + "host": "http://espocrm", + "api_key": "", + "secret_key": "" + }, + "default_client": { + "redirect_uri": "http://localhost:2522/login-ok" + }, + "oidc_signing_key": "/test_oidc_key.pem", + "oidc_public_key": "/test_oidc_key.pem.pub", + "oidc_issuer": "http://localhost:2521" +} \ No newline at end of file diff --git a/server/Cargo.lock b/server/Cargo.lock index 791deb0..e743239 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -4,11 +4,11 @@ version = 3 [[package]] name = "actix-codec" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "bytes", "futures-core", "futures-sink", @@ -36,17 +36,17 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.5.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "129d4c88e98860e1758c5de288d1632b07970a16d59bdf7b8d66053d582bb71f" +checksum = "d223b13fd481fc0d1f83bb12659ae774d9e3601814c68a0bc539731698cca743" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", "ahash", - "base64 0.21.5", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.5.0", "brotli", "bytes", "bytestring", @@ -80,7 +80,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] @@ -171,9 +171,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.4.1" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e43428f3bf11dee6d166b00ec2df4e3aa8cc1606aaa0b7433c146852e2f4e03b" +checksum = "43a6556ddebb638c2358714d853257ed226ece6023ef9364f23f0c70737ea984" dependencies = [ "actix-codec", "actix-http", @@ -218,7 +218,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] @@ -238,9 +238,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", @@ -251,9 +251,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -275,9 +275,27 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "anyhow" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "atoi" @@ -288,27 +306,17 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic-write-file" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" -dependencies = [ - "nix", - "rand", -] - [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -319,6 +327,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -327,9 +341,15 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" @@ -337,6 +357,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "binstring" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0d60973d9320722cb1206f412740e162a33b8547ea8d6be75d7cff237c7a85" + [[package]] name = "bitflags" version = "1.3.2" @@ -345,13 +371,24 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" dependencies = [ "serde", ] +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -363,9 +400,9 @@ dependencies = [ [[package]] name = "brotli" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -384,9 +421,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -396,9 +433,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bytestring" @@ -411,12 +448,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -425,11 +463,22 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "coarsetime" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -458,6 +507,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "convert_case" version = "0.4.0" @@ -493,18 +548,18 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -517,30 +572,38 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-queue" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc6598521bb5a83d491e8c1fe51db7296019d2ca3cb93cc6c2a20369a4d78a2" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.18" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a430a770ebd84726f584a90ee7f020d28db52c6d02138900f22341f866d39c" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "cfg-if", + "generic-array", + "rand_core", + "subtle", + "zeroize", ] [[package]] @@ -553,11 +616,19 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-codecs" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b7eb4404b8195a9abb6356f4ac07d8ba267045c8d6d220ac4dc992e6cc75df" + [[package]] name = "database" version = "0.1.0" dependencies = [ + "jwt-simple", "rand", + "serde", "sqlx", "thiserror", "time", @@ -566,9 +637,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -577,9 +648,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -615,20 +686,65 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519-compact" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190" +dependencies = [ + "ct-codecs", + "getrandom", +] + [[package]] name = "either" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" dependencies = [ "serde", ] +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -650,9 +766,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -692,9 +808,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "eyre" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ "indenter", "once_cell", @@ -702,9 +818,19 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] [[package]] name = "finl_unicode" @@ -714,9 +840,9 @@ checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -800,7 +926,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] @@ -840,17 +966,20 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -859,11 +988,22 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "h2" -version = "0.3.22" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", @@ -880,9 +1020,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", @@ -908,9 +1048,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -936,6 +1076,30 @@ dependencies = [ "digest", ] +[[package]] +name = "hmac-sha1-compact" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9d405ec732fa3fcde87264e54a32a84956a377b3e3107de96e59b798c84a7" + +[[package]] +name = "hmac-sha256" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3688e69b38018fec1557254f64c8dc2cc8ec502890182f395dbb0aa997aa5735" +dependencies = [ + "digest", +] + +[[package]] +name = "hmac-sha512" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ce1f4656bae589a3fab938f9f09bf58645b7ed01a2c5f8a3c238e01a4ef78a" +dependencies = [ + "digest", +] + [[package]] name = "home" version = "0.5.9" @@ -947,9 +1111,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1035,9 +1199,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -1051,37 +1215,77 @@ checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt-simple" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "094661f5aad510abe2658bff20409e89046b753d9dc2d4007f5c100b6d982ba0" +dependencies = [ + "anyhow", + "binstring", + "blake2b_simd", + "coarsetime", + "ct-codecs", + "ed25519-compact", + "hmac-sha1-compact", + "hmac-sha256", + "hmac-sha512", + "k256", + "p256", + "p384", + "rand", + "serde", + "serde_json", + "superboring", + "thiserror", + "zeroize", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1099,9 +1303,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libm" @@ -1122,9 +1326,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "local-channel" @@ -1145,9 +1349,9 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1155,9 +1359,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "matchers" @@ -1180,9 +1384,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mime" @@ -1198,18 +1402,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -1223,17 +1427,6 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d02c0b00610773bb7fc61d85e13d86c7858cbdf00e1a120bfc41bc055dbaa0e" -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "libc", -] - [[package]] name = "noiseless-tracing-actix-web" version = "0.1.0" @@ -1282,21 +1475,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -1305,9 +1503,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -1350,11 +1548,35 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -1362,22 +1584,32 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.1", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] [[package]] name = "pem-rfc7468" @@ -1396,29 +1628,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1449,9 +1681,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" @@ -1465,11 +1697,20 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] @@ -1485,9 +1726,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1531,16 +1772,25 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.5.0", +] + [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", - "regex-syntax 0.8.2", + "regex-automata 0.4.6", + "regex-syntax 0.8.3", ] [[package]] @@ -1554,13 +1804,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.3", ] [[package]] @@ -1571,17 +1821,17 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", "bytes", "encoding_rs", "futures-core", @@ -1603,6 +1853,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-rustls", @@ -1615,18 +1866,29 @@ dependencies = [ "winreg", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1643,6 +1905,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core", + "sha2", "signature", "spki", "subtle", @@ -1651,9 +1914,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" @@ -1666,11 +1929,11 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -1679,9 +1942,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.10" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", @@ -1695,7 +1958,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", ] [[package]] @@ -1710,9 +1973,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scopeguard" @@ -1730,37 +1993,51 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "semver" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -1823,9 +2100,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1851,18 +2128,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1903,9 +2180,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1916,9 +2193,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ "ahash", "atoi", @@ -1926,7 +2203,6 @@ dependencies = [ "bytes", "crc", "crossbeam-queue", - "dotenvy", "either", "event-listener", "futures-channel", @@ -1959,9 +2235,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" dependencies = [ "proc-macro2", "quote", @@ -1972,11 +2248,10 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ - "atomic-write-file", "dotenvy", "either", "heck", @@ -1998,13 +2273,13 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ "atoi", - "base64 0.21.5", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.5.0", "byteorder", "bytes", "crc", @@ -2040,13 +2315,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" dependencies = [ "atoi", - "base64 0.21.5", - "bitflags 2.4.1", + "base64 0.21.7", + "bitflags 2.5.0", "byteorder", "crc", "dotenvy", @@ -2067,7 +2342,6 @@ dependencies = [ "rand", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlx-core", @@ -2079,9 +2353,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" dependencies = [ "atoi", "flume", @@ -2117,6 +2391,19 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "superboring" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbde97f499e51ef384f585dc8f8fb6a9c3a71b274b8d12469b516758e6540607" +dependencies = [ + "getrandom", + "hmac-sha256", + "hmac-sha512", + "rand", + "rsa", +] + [[package]] name = "syn" version = "1.0.109" @@ -2130,15 +2417,21 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2168,42 +2461,41 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -2211,12 +2503,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", @@ -2231,10 +2524,11 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] @@ -2255,9 +2549,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -2280,7 +2574,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] @@ -2295,9 +2589,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -2306,16 +2600,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2338,9 +2631,9 @@ dependencies = [ [[package]] name = "tracing-actix-web" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe0d5feac3f4ca21ba33496bcb1ccab58cca6412b1405ae80f0581541e0ca78" +checksum = "fa069bd1503dd526ee793bb3fce408895136c95fc86d2edb2acf1c646d7f0684" dependencies = [ "actix-web", "mutually_exclusive_features", @@ -2357,7 +2650,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] @@ -2423,9 +2716,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -2435,18 +2728,18 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode_categories" @@ -2479,9 +2772,9 @@ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] name = "uuid" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] @@ -2519,11 +2812,26 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2531,24 +2839,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.39" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -2558,9 +2866,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2568,28 +2876,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -2597,15 +2905,19 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", +] [[package]] name = "wilford" @@ -2615,13 +2927,15 @@ dependencies = [ "actix-multiresponse", "actix-route-config", "actix-web", - "base64 0.21.5", + "base64 0.21.7", "color-eyre", "database", "envy", "espocrm-rs", "noiseless-tracing-actix-web", + "pem", "reqwest", + "rsa", "serde", "serde_json", "serde_qs", @@ -2630,6 +2944,7 @@ dependencies = [ "tokio", "tracing", "tracing-actix-web", + "tracing-error", "tracing-subscriber", ] @@ -2670,7 +2985,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -2690,17 +3005,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -2711,9 +3027,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -2723,9 +3039,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -2735,9 +3051,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -2747,9 +3069,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -2759,9 +3081,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -2771,9 +3093,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -2783,9 +3105,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winreg" @@ -2799,22 +3121,22 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.63", ] [[package]] @@ -2825,27 +3147,27 @@ checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" [[package]] name = "zstd" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.0.0" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", diff --git a/server/database/Cargo.toml b/server/database/Cargo.toml index 2d27c16..d7ece18 100644 --- a/server/database/Cargo.toml +++ b/server/database/Cargo.toml @@ -3,11 +3,11 @@ name = "database" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] rand = "0.8.5" sqlx = { version = "0.7.3", features = ["mysql", "migrate", "runtime-tokio-rustls"] } thiserror = "1.0.51" time = "0.3.31" tracing = "0.1.40" +jwt-simple = { version = "0.12.9", default-features = false, features = ["pure-rust"]} +serde = { version = "1.0.193", features = ["derive"] } \ No newline at end of file diff --git a/server/database/migrations/1_initial.sql b/server/database/migrations/1_initial.sql index 2a59903..d154051 100644 --- a/server/database/migrations/1_initial.sql +++ b/server/database/migrations/1_initial.sql @@ -12,8 +12,9 @@ CREATE TABLE oauth2_pending_authorizations ( client_id VARCHAR(32) NOT NULL, scopes TEXT DEFAULT NULL, state TEXT DEFAULT NULL, - espo_user_id TEXT DEFAULT NULL, + user_id TEXT DEFAULT NULL, ty TEXT NOT NULL, + nonce TEXT DEFAULT NULL, PRIMARY KEY (id) ); @@ -22,7 +23,7 @@ CREATE TABLE oauth2_access_tokens ( client_id VARCHAR(32) NOT NULL, expires_at BIGINT NOT NULL, issued_at BIGINT NOT NULL, - espo_user_id VARCHAR(64) NOT NULL, + user_id VARCHAR(64) NOT NULL, scopes TEXT DEFAULT NULL, PRIMARY KEY (token) ); @@ -30,7 +31,7 @@ CREATE TABLE oauth2_access_tokens ( CREATE TABLE oauth2_refresh_tokens ( token VARCHAR(32) NOT NULL, client_id VARCHAR(32) NOT NULL, - espo_user_id VARCHAR(64) NOT NULL, + user_id VARCHAR(64) NOT NULL, scopes TEXT DEFAULT NULL, PRIMARY KEY (token) ); @@ -40,21 +41,23 @@ CREATE TABLE oauth2_authorization_codes ( code VARCHAR(32) NOT NULL, expires_at BIGINT NOT NULL, scopes TEXT DEFAULT NULL, - espo_user_id TEXT NOT NULL, + user_id TEXT NOT NULL, + nonce TEXT DEFAULT NULL, PRIMARY KEY (code) ); CREATE TABLE users ( - espo_user_id VARCHAR(64) NOT NULL, + user_id VARCHAR(64) NOT NULL, name TEXT NOT NULL, - is_espo_admin BOOL, - PRIMARY KEY (espo_user_id) + email TEXT NOT NULL, + is_admin BOOL, + PRIMARY KEY (user_id) ); CREATE TABLE user_permitted_scopes ( - espo_user_id VARCHAR(64) NOT NULL, + user_id VARCHAR(64) NOT NULL, scope VARCHAR(64) NOT NULL, - PRIMARY KEY (espo_user_id, scope) + PRIMARY KEY (user_id, scope) ); CREATE TABLE constant_access_tokens ( diff --git a/server/database/src/constant_access_tokens.rs b/server/database/src/constant_access_tokens.rs index f7d3420..251d469 100644 --- a/server/database/src/constant_access_tokens.rs +++ b/server/database/src/constant_access_tokens.rs @@ -2,6 +2,7 @@ use crate::driver::Database; use crate::generate_string; use sqlx::FromRow; use sqlx::Result; +use tracing::instrument; #[derive(Debug, Clone, FromRow)] pub struct ConstantAccessToken { @@ -14,6 +15,7 @@ impl ConstantAccessToken { generate_string(32) } + #[instrument] pub async fn new(driver: &Database, name: String) -> Result { let token = Self::generate_token(); @@ -26,12 +28,14 @@ impl ConstantAccessToken { Ok(Self { name, token }) } + #[instrument] pub async fn list(driver: &Database) -> Result> { Ok(sqlx::query_as("SELECT * FROM constant_access_tokens") .fetch_all(&**driver) .await?) } + #[instrument] pub async fn get_by_token(driver: &Database, token: &str) -> Result> { Ok( sqlx::query_as("SELECT * FROM constant_access_tokens WHERE token = ?") @@ -41,6 +45,7 @@ impl ConstantAccessToken { ) } + #[instrument] pub async fn revoke(self, driver: &Database) -> Result<()> { sqlx::query("DELETE FROM constant_access_tokens WHERE token = ?") .bind(self.token) diff --git a/server/database/src/oauth2_client.rs b/server/database/src/oauth2_client.rs index 764594d..14a77bb 100644 --- a/server/database/src/oauth2_client.rs +++ b/server/database/src/oauth2_client.rs @@ -1,9 +1,14 @@ use crate::driver::Database; +use crate::user::User; use crate::{generate_string, impl_enum_type}; +use jwt_simple::algorithms::{RS256KeyPair, RSAKeyPairLike}; +use jwt_simple::claims::Claims; +use serde::{Deserialize, Serialize}; use sqlx::{Decode, Encode, FromRow, Result}; use std::collections::HashSet; use thiserror::Error; use time::{Duration, OffsetDateTime}; +use tracing::instrument; #[derive(Debug, Clone, FromRow)] pub struct OAuth2Client { @@ -16,43 +21,50 @@ pub struct OAuth2Client { #[derive(Debug, Clone)] pub enum OAuth2PendingAuthorization { - EspoUnauthorized(OAuth2PendingAuthorizationUnauthorized), - EspoAuthorized(OAuth2PendingAuthorizationAuthorized), + Unauthorized(OAuth2PendingAuthorizationUnauthorized), + Authorized(OAuth2PendingAuthorizationAuthorized), } impl OAuth2PendingAuthorization { pub fn ty(&self) -> &AuthorizationType { match self { - Self::EspoUnauthorized(v) => &v.ty, - Self::EspoAuthorized(v) => &v.ty, + Self::Unauthorized(v) => &v.ty, + Self::Authorized(v) => &v.ty, } } pub fn id(&self) -> &String { match self { - Self::EspoAuthorized(v) => &v.id, - Self::EspoUnauthorized(v) => &v.id, + Self::Authorized(v) => &v.id, + Self::Unauthorized(v) => &v.id, } } pub fn client_id(&self) -> &String { match self { - Self::EspoAuthorized(v) => &v.client_id, - Self::EspoUnauthorized(v) => &v.client_id, + Self::Authorized(v) => &v.client_id, + Self::Unauthorized(v) => &v.client_id, } } pub fn state(&self) -> &Option { match self { - Self::EspoAuthorized(v) => &v.state, - Self::EspoUnauthorized(v) => &v.state, + Self::Authorized(v) => &v.state, + Self::Unauthorized(v) => &v.state, } } pub fn scopes(&self) -> &Option { match self { - Self::EspoAuthorized(v) => &v.scopes, - Self::EspoUnauthorized(v) => &v.scopes, + Self::Authorized(v) => &v.scopes, + Self::Unauthorized(v) => &v.scopes, + } + } + + pub fn nonce(&self) -> &Option { + match self { + Self::Authorized(v) => &v.nonce, + Self::Unauthorized(v) => &v.nonce, } } } @@ -64,6 +76,7 @@ pub struct OAuth2PendingAuthorizationUnauthorized { scopes: Option, state: Option, ty: AuthorizationType, + nonce: Option, } #[derive(Debug, Clone)] @@ -72,8 +85,9 @@ pub struct OAuth2PendingAuthorizationAuthorized { client_id: String, scopes: Option, state: Option, - espo_user_id: String, + user_id: String, ty: AuthorizationType, + nonce: Option, } #[derive(FromRow)] @@ -82,17 +96,19 @@ struct _OAuth2PendingAuthorization { client_id: String, scopes: Option, state: Option, - espo_user_id: Option, + user_id: Option, ty: AuthorizationType, + nonce: Option, } -#[derive(FromRow)] +#[derive(Debug, FromRow)] pub struct OAuth2AuthorizationCode { pub code: String, pub client_id: String, pub expires_at: i64, pub scopes: Option, - pub espo_user_id: String, + pub user_id: String, + pub nonce: Option, } #[derive(Clone, Debug, FromRow)] @@ -101,15 +117,15 @@ pub struct AccessToken { pub client_id: String, pub expires_at: i64, pub issued_at: i64, - pub espo_user_id: String, + pub user_id: String, pub scopes: Option, } -#[derive(FromRow)] +#[derive(Debug, FromRow)] pub struct RefreshToken { pub token: String, pub client_id: String, - pub espo_user_id: String, + pub user_id: String, pub scopes: Option, } @@ -117,12 +133,12 @@ pub struct RefreshToken { pub enum OAuth2AuthorizationCodeCreationError { #[error("{0}")] Sqlx(#[from] sqlx::Error), - #[error("Not authorized with EspoCRM yet")] + #[error("Not authorized yet")] Unauthorized, } #[derive(Debug, Error)] -pub enum OAuth2PendingAuthorizationSetEspoIdError { +pub enum OAuth2PendingAuthorizationSetUserIdError { #[error("{0}")] Sqlx(#[from] sqlx::Error), #[error("Cannot overwrite existing authorization")] @@ -133,6 +149,7 @@ pub enum OAuth2PendingAuthorizationSetEspoIdError { pub enum AuthorizationType { AuthorizationCode, Implicit, + IdToken, } impl_enum_type!(AuthorizationType); @@ -170,6 +187,7 @@ impl OAuth2Client { (OffsetDateTime::now_utc() + Duration::hours(1)).unix_timestamp() } + #[instrument] pub async fn new( driver: &Database, name: String, @@ -197,12 +215,14 @@ impl OAuth2Client { }) } + #[instrument] pub async fn list(driver: &Database) -> Result> { Ok(sqlx::query_as("SELECT * FROM oauth2_clients") .fetch_all(&**driver) .await?) } + #[instrument] pub async fn delete(self, driver: &Database) -> Result<()> { sqlx::query("DELETE FROM oauth2_clients WHERE client_id = ?") .bind(self.client_id) @@ -211,6 +231,7 @@ impl OAuth2Client { Ok(()) } + #[instrument] pub async fn get_by_client_id(driver: &Database, client_id: &str) -> Result> { Ok( sqlx::query_as("SELECT * FROM oauth2_clients WHERE client_id = ?") @@ -220,42 +241,47 @@ impl OAuth2Client { ) } + #[instrument] pub async fn new_pending_authorization( &self, driver: &Database, scopes: Option, state: Option, ty: AuthorizationType, + nonce: Option, ) -> Result { let id = Self::generate_pending_authorization_id(); - sqlx::query("INSERT INTO oauth2_pending_authorizations (id, client_id, scopes, state, ty) VALUES (?, ?, ?, ?, ?)") + sqlx::query("INSERT INTO oauth2_pending_authorizations (id, client_id, scopes, state, ty, nonce) VALUES (?, ?, ?, ?, ?, ?)") .bind(&id) .bind(&self.client_id) .bind(&scopes) .bind(&state) .bind(&ty) + .bind(&nonce) .execute(&**driver) .await?; - Ok(OAuth2PendingAuthorization::EspoUnauthorized( + Ok(OAuth2PendingAuthorization::Unauthorized( OAuth2PendingAuthorizationUnauthorized { id, client_id: self.client_id.clone(), scopes, state, ty, + nonce, }, )) } + #[instrument] pub async fn new_authorization_code( &self, driver: &Database, pending: OAuth2PendingAuthorization, ) -> std::result::Result { let pending = match pending { - OAuth2PendingAuthorization::EspoAuthorized(v) => v, - OAuth2PendingAuthorization::EspoUnauthorized(_) => { + OAuth2PendingAuthorization::Authorized(v) => v, + OAuth2PendingAuthorization::Unauthorized(_) => { return Err(OAuth2AuthorizationCodeCreationError::Unauthorized) } }; @@ -265,12 +291,13 @@ impl OAuth2Client { let mut tx = driver.begin().await?; - sqlx::query("INSERT INTO oauth2_authorization_codes (client_id, code, expires_at, scopes, espo_user_id) VALUES (?, ?, ?, ?, ?)") + sqlx::query("INSERT INTO oauth2_authorization_codes (client_id, code, expires_at, scopes, user_id, nonce) VALUES (?, ?, ?, ?, ?, ?)") .bind(&self.client_id) .bind(&code) .bind(expires_at) .bind(&pending.scopes) - .bind(&pending.espo_user_id) + .bind(&pending.user_id) + .bind(&pending.nonce) .execute(&mut *tx) .await?; @@ -286,18 +313,20 @@ impl OAuth2Client { code, scopes: pending.scopes.clone(), expires_at, - espo_user_id: pending.espo_user_id, + user_id: pending.user_id, + nonce: pending.nonce, }) } + #[instrument] pub async fn new_access_token( &self, driver: &Database, authorization: OAuth2PendingAuthorization, ) -> std::result::Result { let authorization = match authorization { - OAuth2PendingAuthorization::EspoAuthorized(v) => v, - OAuth2PendingAuthorization::EspoUnauthorized(_) => { + OAuth2PendingAuthorization::Authorized(v) => v, + OAuth2PendingAuthorization::Unauthorized(_) => { return Err(OAuth2AuthorizationCodeCreationError::Unauthorized) } }; @@ -308,12 +337,12 @@ impl OAuth2Client { let mut tx = driver.begin().await?; - sqlx::query("INSERT INTO oauth2_access_tokens (token, client_id, expires_at, issued_at, espo_user_id, scopes) VALUES (?, ?, ?, ?, ?, ?)") + sqlx::query("INSERT INTO oauth2_access_tokens (token, client_id, expires_at, issued_at, user_id, scopes) VALUES (?, ?, ?, ?, ?, ?)") .bind(&atoken) .bind(&self.client_id) .bind(expires_at) .bind(issued_at) - .bind(&authorization.espo_user_id) + .bind(&authorization.user_id) .bind(&authorization.scopes) .execute(&mut *tx) .await?; @@ -329,12 +358,13 @@ impl OAuth2Client { token: atoken, issued_at, expires_at, - espo_user_id: authorization.espo_user_id, + user_id: authorization.user_id, scopes: authorization.scopes, client_id: self.client_id.clone(), }) } + #[instrument] pub async fn new_token_pair( &self, driver: &Database, @@ -348,21 +378,21 @@ impl OAuth2Client { let mut tx = driver.begin().await?; // Access token - sqlx::query("INSERT INTO oauth2_access_tokens (token, client_id, expires_at, issued_at, espo_user_id, scopes) VALUES (?, ?, ?, ?, ?, ?)") + sqlx::query("INSERT INTO oauth2_access_tokens (token, client_id, expires_at, issued_at, user_id, scopes) VALUES (?, ?, ?, ?, ?, ?)") .bind(&atoken) .bind(&self.client_id) .bind(expires_at) .bind(issued_at) - .bind(&authorization.espo_user_id) + .bind(&authorization.user_id) .bind(&authorization.scopes) .execute(&mut *tx) .await?; // Refresh token - sqlx::query("INSERT INTO oauth2_refresh_tokens (token, client_id, espo_user_id, scopes) VALUES (?, ?, ?, ?)") + sqlx::query("INSERT INTO oauth2_refresh_tokens (token, client_id, user_id, scopes) VALUES (?, ?, ?, ?)") .bind(&rtoken) .bind(&self.client_id) - .bind(&authorization.espo_user_id) + .bind(&authorization.user_id) .bind(&authorization.scopes) .execute(&mut *tx) .await?; @@ -381,18 +411,19 @@ impl OAuth2Client { client_id: self.client_id.clone(), expires_at, issued_at, - espo_user_id: authorization.espo_user_id.clone(), + user_id: authorization.user_id.clone(), scopes: authorization.scopes.clone(), }, RefreshToken { token: rtoken, client_id: self.client_id.clone(), - espo_user_id: authorization.espo_user_id.clone(), + user_id: authorization.user_id.clone(), scopes: authorization.scopes.clone(), }, )) } + #[instrument] pub async fn refresh_access_token( &self, driver: &Database, @@ -402,11 +433,11 @@ impl OAuth2Client { let expires_at = Self::generate_access_token_expiry(); let issued_at = OffsetDateTime::now_utc().unix_timestamp(); - sqlx::query("INSERT INTO oauth2_access_tokens (token, client_id, expires_at, issued_at, espo_user_id, scopes) VALUES (?, ?, ?, ?, ?, ?)") + sqlx::query("INSERT INTO oauth2_access_tokens (token, client_id, expires_at, issued_at, user_id, scopes) VALUES (?, ?, ?, ?, ?, ?)") .bind(&atoken) .bind(&self.client_id) .bind(expires_at) - .bind(&refresh_token.espo_user_id) + .bind(&refresh_token.user_id) .bind(&refresh_token.scopes) .execute(&**driver) .await?; @@ -417,12 +448,13 @@ impl OAuth2Client { scopes: refresh_token.scopes.clone(), issued_at, expires_at, - espo_user_id: refresh_token.espo_user_id.clone(), + user_id: refresh_token.user_id.clone(), }) } } impl AccessToken { + #[instrument] pub async fn get_by_token(driver: &Database, token: &str) -> Result> { Ok( sqlx::query_as("SELECT * FROM oauth2_access_tokens WHERE token = ?") @@ -432,6 +464,7 @@ impl AccessToken { ) } + #[instrument] pub async fn get_with_validation( driver: &Database, token: &str, @@ -452,6 +485,7 @@ impl AccessToken { ) } + #[instrument] pub fn scopes(&self) -> HashSet { self.scopes .as_ref() @@ -461,6 +495,7 @@ impl AccessToken { } impl RefreshToken { + #[instrument] pub async fn get_by_token(driver: &Database, token: &str) -> Result> { Ok( sqlx::query_as("SELECT * FROM oauth2_refresh_tokens WHERE token = ?") @@ -472,6 +507,7 @@ impl RefreshToken { } impl OAuth2PendingAuthorization { + #[instrument] pub async fn get_by_id( driver: &Database, id: &str, @@ -485,36 +521,36 @@ impl OAuth2PendingAuthorization { ) } - pub async fn set_espo_user_id( + #[instrument] + pub async fn set_user_id( self, driver: &Database, - espo_user_id: &str, - ) -> std::result::Result { + user_id: &str, + ) -> std::result::Result { let id = match &self { - Self::EspoUnauthorized(v) => &v.id, - Self::EspoAuthorized(_) => { - return Err(OAuth2PendingAuthorizationSetEspoIdError::AlreadyAuthorized) + Self::Unauthorized(v) => &v.id, + Self::Authorized(_) => { + return Err(OAuth2PendingAuthorizationSetUserIdError::AlreadyAuthorized) } }; - sqlx::query("UPDATE oauth2_pending_authorizations SET espo_user_id = ? WHERE id = ?") - .bind(espo_user_id) + sqlx::query("UPDATE oauth2_pending_authorizations SET user_id = ? WHERE id = ?") + .bind(user_id) .bind(&id) .execute(&**driver) .await?; let new_self = match self { - Self::EspoUnauthorized(v) => { - Self::EspoAuthorized(OAuth2PendingAuthorizationAuthorized { - id: v.id, - client_id: v.client_id, - espo_user_id: espo_user_id.to_string(), - state: v.state, - scopes: v.scopes, - ty: v.ty, - }) - } - Self::EspoAuthorized(_) => unreachable!(), + Self::Unauthorized(v) => Self::Authorized(OAuth2PendingAuthorizationAuthorized { + id: v.id, + client_id: v.client_id, + user_id: user_id.to_string(), + state: v.state, + scopes: v.scopes, + ty: v.ty, + nonce: v.nonce, + }), + Self::Authorized(_) => unreachable!(), }; Ok(new_self) @@ -522,6 +558,7 @@ impl OAuth2PendingAuthorization { } impl OAuth2AuthorizationCode { + #[instrument] pub async fn get_by_code(driver: &Database, code: &str) -> Result> { Ok( sqlx::query_as("SELECT * FROM oauth2_authorization_codes WHERE code = ?") @@ -534,23 +571,109 @@ impl OAuth2AuthorizationCode { impl From<_OAuth2PendingAuthorization> for OAuth2PendingAuthorization { fn from(value: _OAuth2PendingAuthorization) -> Self { - if let Some(espo_user_id) = value.espo_user_id { - Self::EspoAuthorized(OAuth2PendingAuthorizationAuthorized { + if let Some(user_id) = value.user_id { + Self::Authorized(OAuth2PendingAuthorizationAuthorized { id: value.id, client_id: value.client_id, scopes: value.scopes, state: value.state, - espo_user_id, + user_id, ty: value.ty, + nonce: value.nonce, }) } else { - Self::EspoUnauthorized(OAuth2PendingAuthorizationUnauthorized { + Self::Unauthorized(OAuth2PendingAuthorizationUnauthorized { id: value.id, client_id: value.client_id, scopes: value.scopes, state: value.state, ty: value.ty, + nonce: value.nonce, }) } } } + +#[derive(Serialize, Deserialize)] +pub struct IdTokenClaims { + /// Issuer Identifier for the Issuer of the response. The iss value is a case-sensitive URL using the https scheme that contains scheme, host, and optionally, port number and path components and no query or fragment components. + iss: String, + /// Subject Identifier. + /// A locally unique and never reassigned identifier within the Issuer for the End-User, which is intended to be consumed by the Client, e.g., 24400320 or AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4. + /// It MUST NOT exceed 255 ASCII [RFC20] characters in length. The sub value is a case-sensitive string. + sub: String, + /// Audience(s) that this ID Token is intended for. It MUST contain the OAuth 2.0 client_id of the Relying Party as an audience value. It MAY also contain identifiers for other audiences. + /// In the general case, the aud value is an array of case-sensitive strings. In the common special case when there is one audience, the aud value MAY be a single case-sensitive string. + aud: String, + /// Expiration time on or after which the ID Token MUST NOT be accepted anymore. + exp: i64, + /// Time at which the JWT was issued. Its value is a JSON number representing the number of seconds from 1970-01-01T00:00:00Z as measured in UTC until the date/time. + iat: i64, + /// String value used to associate a Client session with an ID Token, and to mitigate replay attacks. The value is passed through unmodified from the Authentication Request to the ID Token + nonce: Option, + /// Authorized party - the party to which the ID Token was issued. If present, it MUST contain the OAuth 2.0 Client ID of this party. + azp: String, + + // We also have some custom claims, this is allowed by the JWT spec + sub_email: String, + sub_name: String, + sub_is_admin: bool, +} + +#[derive(Debug)] +pub enum JwtSigningAlgorithm { + RS256, +} + +#[derive(Debug, Error)] +pub enum IdTokenCreationError { + #[error("Invalid keypair: {0}")] + Keypair(String), + #[error("Signing failed: {0}")] + Signing(String), +} + +#[instrument] +pub fn create_id_token( + issuer: String, + client: &OAuth2Client, + user: &User, + oidc_signing_key_pem: &str, + access_token: &AccessToken, + nonce: Option, + jwt_signing_algorithm: JwtSigningAlgorithm, +) -> std::result::Result { + let iat = OffsetDateTime::now_utc(); + + let id_claims = IdTokenClaims { + // Standard claims + iss: issuer, + sub: user.user_id.clone(), + aud: client.client_id.clone(), + exp: access_token.expires_at, + iat: iat.unix_timestamp(), + nonce, + azp: client.client_id.clone(), + // Custom claims + sub_name: user.name.clone(), + sub_is_admin: user.is_admin, + sub_email: user.email.clone(), + }; + + match jwt_signing_algorithm { + JwtSigningAlgorithm::RS256 => { + let key = RS256KeyPair::from_pem(oidc_signing_key_pem) + .map_err(|e| IdTokenCreationError::Keypair(e.to_string()))?; + + let claims = Claims::with_custom_claims( + id_claims, + jwt_simple::reexports::coarsetime::Duration::from_secs( + (access_token.expires_at - iat.unix_timestamp()) as u64, + ), + ); + Ok(key + .sign(claims) + .map_err(|e| IdTokenCreationError::Signing(e.to_string()))?) + } + } +} diff --git a/server/database/src/user.rs b/server/database/src/user.rs index 3d385c3..f5dc0b7 100644 --- a/server/database/src/user.rs +++ b/server/database/src/user.rs @@ -1,59 +1,69 @@ use crate::driver::Database; use sqlx::{FromRow, Result}; +use tracing::instrument; #[derive(Debug, FromRow)] pub struct User { - pub espo_user_id: String, + pub user_id: String, pub name: String, - pub is_espo_admin: bool, + pub email: String, + pub is_admin: bool, } impl User { + #[instrument] pub async fn new( driver: &Database, - espo_user_id: String, + user_id: String, name: String, - is_espo_admin: bool, + email: String, + is_admin: bool, ) -> Result { - sqlx::query("INSERT INTO users (espo_user_id, name, is_espo_admin) VALUES (?, ?, ?)") - .bind(&espo_user_id) + sqlx::query("INSERT INTO users (user_id, name, email, is_admin) VALUES (?, ?, ?, ?)") + .bind(&user_id) .bind(&name) - .bind(is_espo_admin) + .bind(&email) + .bind(is_admin) .execute(&**driver) .await?; Ok(Self { name, - espo_user_id, - is_espo_admin, + user_id, + email, + is_admin, }) } + #[instrument] pub async fn get_by_id(driver: &Database, id: &str) -> Result> { - Ok(sqlx::query_as("SELECT * FROM users WHERE espo_user_id = ?") + Ok(sqlx::query_as("SELECT * FROM users WHERE user_id = ?") .bind(id) .fetch_optional(&**driver) .await?) } + #[instrument] pub async fn list(driver: &Database) -> Result> { Ok(sqlx::query_as("SELECT * FROM users") .fetch_all(&**driver) .await?) } + #[instrument] pub async fn list_permitted_scopes(&self, driver: &Database) -> Result> { Ok( - sqlx::query_scalar("SELECT scope FROM user_permitted_scopes WHERE espo_user_id = ?") - .bind(&self.espo_user_id) + sqlx::query_scalar("SELECT scope FROM user_permitted_scopes WHERE user_id = ?") + .bind(&self.user_id) .fetch_all(&**driver) .await?, ) } + #[instrument] pub async fn remove_permitted_scope(&self, driver: &Database, scope: &str) -> Result<()> { - sqlx::query("DELETE FROM user_permitted_scopes WHERE espo_user_id = ? AND scope = ?") - .bind(&self.espo_user_id) + sqlx::query("DELETE FROM user_permitted_scopes WHERE user_id = ? AND scope = ?") + .bind(&self.user_id) .bind(scope) .execute(&**driver) .await?; @@ -61,9 +71,10 @@ impl User { Ok(()) } + #[instrument] pub async fn grant_permitted_scope(&self, driver: &Database, scope: &str) -> Result<()> { - sqlx::query("INSERT INTO user_permitted_scopes (espo_user_id, scope) VALUES (?, ?)") - .bind(&self.espo_user_id) + sqlx::query("INSERT INTO user_permitted_scopes (user_id, scope) VALUES (?, ?)") + .bind(&self.user_id) .bind(scope) .execute(&**driver) .await?; diff --git a/server/wilford/Cargo.toml b/server/wilford/Cargo.toml index e87da5f..4437fae 100644 --- a/server/wilford/Cargo.toml +++ b/server/wilford/Cargo.toml @@ -24,3 +24,6 @@ tokio = { version = "1.35.1", features = ["full"] } tracing = "0.1.40" tracing-actix-web = "0.7.9" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +pem = "3.0.4" +tracing-error = "0.2.0" +rsa = "0.9.6" \ No newline at end of file diff --git a/server/wilford/src/config.rs b/server/wilford/src/config.rs index da99c46..c367b6c 100644 --- a/server/wilford/src/config.rs +++ b/server/wilford/src/config.rs @@ -9,17 +9,24 @@ struct EnvConfig { config_path: PathBuf, } +/* ANCHOR: config */ #[derive(Debug, Deserialize)] pub struct Config { pub http: HttpConfig, pub database: DatabaseConfig, pub espo: EspoConfig, pub default_client: DefaultClientConfig, + pub oidc_signing_key: PathBuf, + pub oidc_public_key: PathBuf, + pub oidc_issuer: String, } #[derive(Debug, Deserialize)] pub struct HttpConfig { pub ui_login_path: String, + pub authorization_endpoint: String, + pub token_endpoint: String, + pub jwks_uri_endpoint: String, } #[derive(Debug, Deserialize)] @@ -41,6 +48,7 @@ pub struct DatabaseConfig { pub struct DefaultClientConfig { pub redirect_uri: String, } +/* ANCHOR_END: config */ impl EnvConfig { fn new() -> Result { @@ -56,6 +64,27 @@ impl Config { Ok(serde_json::from_slice(&buf)?) } + + pub async fn read_oidc_signing_key(&self) -> Result { + Self::read_pem(&self.oidc_signing_key).await + } + + pub async fn read_oidc_public_key(&self) -> Result { + Self::file_read_string(&self.oidc_public_key).await + } + + async fn read_pem(p: &Path) -> Result { + let contents = Self::file_read_string(p).await?; + let pem = pem::parse(contents.as_bytes())?; + Ok(pem.to_string()) + } + + async fn file_read_string(p: &Path) -> Result { + let mut f = fs::File::open(p).await?; + let mut buf = String::new(); + f.read_to_string(&mut buf).await?; + Ok(buf) + } } pub async fn get_config() -> Result { diff --git a/server/wilford/src/espo/user.rs b/server/wilford/src/espo/user.rs index 051dd29..f9c2c22 100644 --- a/server/wilford/src/espo/user.rs +++ b/server/wilford/src/espo/user.rs @@ -2,13 +2,14 @@ use base64::Engine; use espocrm_rs::{EspoApiClient, Method}; use reqwest::{Result, StatusCode}; use serde::Deserialize; -use tracing::warn; +use tracing::{instrument, trace, warn, warn_span, Instrument}; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EspoUser { pub id: String, pub name: String, + pub email_address: String, pub is_active: bool, #[serde(rename(deserialize = "type"))] pub user_type: String, @@ -21,15 +22,19 @@ pub enum LoginStatus { } impl EspoUser { + #[instrument(skip(client))] pub async fn get_by_id(client: &EspoApiClient, id: &str) -> Result { Ok(client .request::<(), &str>(Method::Get, &format!("User/{id}"), None, None) + .instrument(warn_span!("user::by_id")) .await? .error_for_status()? .json() + .instrument(warn_span!("user::by_id::json")) .await?) } + #[instrument(skip_all)] pub async fn try_login( host: &str, username: &str, @@ -50,7 +55,10 @@ impl EspoUser { request = request.header("Espo-Authorization-Code", totp); } - let result = request.send().await?; + let result = request + .send() + .instrument(warn_span!("try_login::request")) + .await?; match result.status() { StatusCode::OK => { @@ -66,7 +74,8 @@ impl EspoUser { is_active: bool, } - let payload: Response = result.json().await?; + trace!("Deserializing EspoCRM response"); + let payload: Response = result.json().instrument(warn_span!("deserialize")).await?; if payload.user.is_active { Ok(LoginStatus::Ok(payload.user.id)) } else { @@ -79,7 +88,8 @@ impl EspoUser { message: String, } - let payload: Response = result.json().await?; + trace!("Deserializing EspoCRM response"); + let payload: Response = result.json().instrument(warn_span!("deserialize")).await?; if payload.message.eq("enterTotpCode") { Ok(LoginStatus::SecondStepRequired) } else { diff --git a/server/wilford/src/main.rs b/server/wilford/src/main.rs index afc1c41..e9a94ad 100644 --- a/server/wilford/src/main.rs +++ b/server/wilford/src/main.rs @@ -1,4 +1,5 @@ use crate::config::{get_config, DefaultClientConfig}; +use crate::routes::{OidcPublicKey, OidcSigningKey, WOidcPublicKey, WOidcSigningKey}; use actix_cors::Cors; use actix_route_config::Routable; use actix_web::{web, App, HttpServer}; @@ -8,7 +9,8 @@ use database::oauth2_client::OAuth2Client; use espocrm_rs::EspoApiClient; use noiseless_tracing_actix_web::NoiselessRootSpanBuilder; use tracing::info; -use tracing_actix_web::{RootSpanBuilder, TracingLogger}; +use tracing_actix_web::TracingLogger; +use tracing_error::ErrorLayer; use tracing_subscriber::fmt::layer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; @@ -16,6 +18,7 @@ use tracing_subscriber::EnvFilter; mod config; mod espo; +mod response_types; mod routes; #[tokio::main] @@ -24,6 +27,7 @@ async fn main() -> Result<()> { install_tracing(); let config = get_config().await?; + let database = Database::new( &config.database.user, &config.database.password, @@ -39,8 +43,12 @@ async fn main() -> Result<()> { ensure_internal_oauth_client_exists(&database, &config.default_client).await?; let w_database = web::Data::new(database); - let w_config = web::Data::new(config); let w_espo = web::Data::new(espo_client); + let w_oidc_signing_key = + WOidcSigningKey::new(OidcSigningKey(config.read_oidc_signing_key().await?)); + let w_oidc_public_key = + WOidcPublicKey::new(OidcPublicKey(config.read_oidc_public_key().await?)); + let w_config = web::Data::new(config); HttpServer::new(move || { App::new() @@ -49,9 +57,11 @@ async fn main() -> Result<()> { .app_data(w_database.clone()) .app_data(w_config.clone()) .app_data(w_espo.clone()) + .app_data(w_oidc_signing_key.clone()) + .app_data(w_oidc_public_key.clone()) .configure(routes::Router::configure) }) - .bind("0.0.0.0:8080")? + .bind("0.0.0.0:2521")? .run() .await?; @@ -88,7 +98,12 @@ fn install_tracing() { } tracing_subscriber::registry() - .with(EnvFilter::from_default_env()) - .with(layer().compact()) + .with( + EnvFilter::from_default_env() + .add_directive("rustls=WARN".parse().expect("Invalid tracing directive")) + .add_directive("rustls=WARN".parse().expect("Invalid tracing directive")), + ) + .with(layer().pretty()) + .with(ErrorLayer::default()) .init(); } diff --git a/server/wilford/src/routes/empty.rs b/server/wilford/src/response_types/empty.rs similarity index 100% rename from server/wilford/src/routes/empty.rs rename to server/wilford/src/response_types/empty.rs diff --git a/server/wilford/src/response_types/mod.rs b/server/wilford/src/response_types/mod.rs new file mode 100644 index 0000000..fd5df81 --- /dev/null +++ b/server/wilford/src/response_types/mod.rs @@ -0,0 +1,6 @@ +mod empty; +pub use empty::Empty; +mod redirect; +pub use redirect::Redirect; +mod uncached; +pub use uncached::Uncached; diff --git a/server/wilford/src/routes/redirect.rs b/server/wilford/src/response_types/redirect.rs similarity index 100% rename from server/wilford/src/routes/redirect.rs rename to server/wilford/src/response_types/redirect.rs diff --git a/server/wilford/src/response_types/uncached.rs b/server/wilford/src/response_types/uncached.rs new file mode 100644 index 0000000..3ecb352 --- /dev/null +++ b/server/wilford/src/response_types/uncached.rs @@ -0,0 +1,26 @@ +use actix_web::http::header::HeaderValue; +use actix_web::{HttpRequest, HttpResponse, Responder}; +use reqwest::header::HeaderName; + +pub struct Uncached(T); + +impl Uncached { + pub fn new(responder: T) -> Self { + Self(responder) + } +} + +impl Responder for Uncached { + type Body = T::Body; + + fn respond_to(self, req: &HttpRequest) -> HttpResponse { + let mut response = self.0.respond_to(req); + let headers = response.headers_mut(); + headers.insert( + HeaderName::from_static("cache-control"), + HeaderValue::from_static("no-store"), + ); + + response + } +} diff --git a/server/wilford/src/routes/appdata.rs b/server/wilford/src/routes/appdata.rs index c28b6f7..c4f2b84 100644 --- a/server/wilford/src/routes/appdata.rs +++ b/server/wilford/src/routes/appdata.rs @@ -6,3 +6,9 @@ use espocrm_rs::EspoApiClient; pub type WDatabase = web::Data; pub type WConfig = web::Data; pub type WEspo = web::Data; + +pub type WOidcSigningKey = web::Data; +pub type WOidcPublicKey = web::Data; + +pub struct OidcSigningKey(pub String); +pub struct OidcPublicKey(pub String); diff --git a/server/wilford/src/routes/auth.rs b/server/wilford/src/routes/auth.rs index 8a393fd..0ef385d 100644 --- a/server/wilford/src/routes/auth.rs +++ b/server/wilford/src/routes/auth.rs @@ -1,6 +1,6 @@ use crate::espo::user::EspoUser; use crate::routes::appdata::{WDatabase, WEspo}; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebError, WebErrorKind, WebResult}; use actix_web::cookie::time::OffsetDateTime; use actix_web::dev::Payload; use actix_web::{FromRequest, HttpRequest}; @@ -39,17 +39,17 @@ impl FromRequest for Auth { let token_info = match AccessToken::get_by_token(&database, &token).await? { Some(v) => { if v.expires_at < OffsetDateTime::now_utc().unix_timestamp() { - return Err(WebError::Unauthorized); + return Err(WebErrorKind::Unauthorized.into()); } else { v } } - None => return Err(WebError::Unauthorized), + None => return Err(WebErrorKind::Unauthorized.into()), }; - let espo_user = EspoUser::get_by_id(&espo_client, &token_info.espo_user_id) + let espo_user = EspoUser::get_by_id(&espo_client, &token_info.user_id) .await - .map_err(|e| WebError::Espo(e))?; + .map_err(|e| WebErrorKind::Espo(e))?; Ok(Self { espo_user_id: espo_user.id, @@ -94,7 +94,7 @@ impl FromRequest for ConstantAccessTokenAuth { let token = get_authorization_token(&req)?; let cat = ConstantAccessToken::get_by_token(&database, &token) .await? - .ok_or(WebError::Unauthorized)?; + .ok_or(WebErrorKind::Unauthorized)?; Ok(Self { name: cat.name, @@ -122,5 +122,5 @@ fn get_authorization_token(req: &HttpRequest) -> WebResult { _ => {} } - Err(WebError::Unauthorized) + Err(WebErrorKind::Unauthorized.into()) } diff --git a/server/wilford/src/routes/error.rs b/server/wilford/src/routes/error.rs index 7bbe760..a4bf216 100644 --- a/server/wilford/src/routes/error.rs +++ b/server/wilford/src/routes/error.rs @@ -1,11 +1,44 @@ use actix_web::http::StatusCode; use actix_web::ResponseError; +use std::fmt; +use std::fmt::{Formatter, Write}; use thiserror::Error; +use tracing_error::SpanTrace; pub type WebResult = Result; +#[derive(Error)] +#[error("{}", kind)] +pub struct WebError { + kind: WebErrorKind, + context: SpanTrace, +} + +impl fmt::Debug for WebError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.kind.fmt(f)?; + f.write_char('\n')?; + f.write_str(&self.context.to_string())?; + f.write_char('\n')?; + Ok(()) + } +} + +impl From for WebError +where + E: Into, +{ + #[track_caller] + fn from(value: E) -> Self { + Self { + kind: value.into(), + context: SpanTrace::capture(), + } + } +} + #[derive(Debug, Error)] -pub enum WebError { +pub enum WebErrorKind { #[error("Not found")] NotFound, #[error("Bad request")] @@ -20,18 +53,24 @@ pub enum WebError { Database(#[from] database::driver::Error), #[error("EspoCRM error: {0}")] Espo(reqwest::Error), + #[error("Internal server error")] + InternalServerError, + #[error("Failed to parse PKCS8 SPKI: {0}")] + RsaPkcs8Spki(#[from] rsa::pkcs8::spki::Error), } impl ResponseError for WebError { fn status_code(&self) -> StatusCode { - match self { - Self::NotFound => StatusCode::NOT_FOUND, - Self::BadRequest => StatusCode::BAD_REQUEST, - Self::Unauthorized => StatusCode::UNAUTHORIZED, - Self::Forbidden => StatusCode::FORBIDDEN, - Self::InvalidInternalState => StatusCode::INTERNAL_SERVER_ERROR, - Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR, - Self::Espo(_) => StatusCode::BAD_GATEWAY, + match self.kind { + WebErrorKind::NotFound => StatusCode::NOT_FOUND, + WebErrorKind::BadRequest => StatusCode::BAD_REQUEST, + WebErrorKind::Unauthorized => StatusCode::UNAUTHORIZED, + WebErrorKind::Forbidden => StatusCode::FORBIDDEN, + WebErrorKind::InvalidInternalState => StatusCode::INTERNAL_SERVER_ERROR, + WebErrorKind::Database(_) => StatusCode::INTERNAL_SERVER_ERROR, + WebErrorKind::Espo(_) => StatusCode::BAD_GATEWAY, + WebErrorKind::InternalServerError => StatusCode::INTERNAL_SERVER_ERROR, + WebErrorKind::RsaPkcs8Spki(_) => StatusCode::INTERNAL_SERVER_ERROR, } } } diff --git a/server/wilford/src/routes/mod.rs b/server/wilford/src/routes/mod.rs index 54c934c..918d972 100644 --- a/server/wilford/src/routes/mod.rs +++ b/server/wilford/src/routes/mod.rs @@ -4,17 +4,18 @@ use actix_web::web::ServiceConfig; mod appdata; mod auth; -mod empty; mod error; mod oauth; -mod redirect; mod v1; +mod well_known; + +pub use appdata::*; pub struct Router; impl Routable for Router { fn configure(config: &mut ServiceConfig) { - config.service( + config.configure(well_known::Router::configure).service( web::scope("/api") .configure(v1::Router::configure) .configure(oauth::Router::configure), diff --git a/server/wilford/src/routes/oauth/authorize.rs b/server/wilford/src/routes/oauth/authorize.rs index 56c6f3a..c704396 100644 --- a/server/wilford/src/routes/oauth/authorize.rs +++ b/server/wilford/src/routes/oauth/authorize.rs @@ -1,6 +1,6 @@ +use crate::response_types::{Redirect, Uncached}; use crate::routes::appdata::{WConfig, WDatabase}; use crate::routes::oauth::{OAuth2AuthorizationResponse, OAuth2Error, OAuth2ErrorKind}; -use crate::routes::redirect::Redirect; use actix_web::web; use database::oauth2_client::{AuthorizationType, OAuth2Client}; use serde::Deserialize; @@ -13,9 +13,10 @@ pub struct Query { redirect_uri: String, scope: Option, state: Option, + nonce: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, PartialEq, Eq, Deserialize)] pub enum ResponseType { #[serde(rename(deserialize = "code"))] /// Authorization Code flow @@ -25,13 +26,25 @@ pub enum ResponseType { /// Implicit Flow /// [RFC6749 Section 4.2](https://datatracker.ietf.org/doc/html/rfc6749#section-4.2) Token, + #[serde(rename(deserialize = "id_token token"))] + IdToken, } pub async fn authorize( database: WDatabase, config: WConfig, query: web::Query, -) -> OAuth2AuthorizationResponse { +) -> OAuth2AuthorizationResponse> { + // Required for the `id_token` response type + // OpenID Connect Core specification, section 3.2.2.1 + if query.response_type.eq(&ResponseType::IdToken) && query.nonce.is_none() { + return OAuth2AuthorizationResponse::Err(OAuth2Error::new( + OAuth2ErrorKind::InvalidRequest, + &query.redirect_uri, + query.state.as_deref(), + )); + } + // Get the OAuth2 client let client = match OAuth2Client::get_by_client_id(&database, &query.client_id).await { Ok(Some(c)) => c, @@ -61,30 +74,15 @@ pub async fn authorize( )); } - let pending_authorization = match query.response_type { - ResponseType::Code => { - // Create authorization - client - .new_pending_authorization( - &database, - query.scope.clone(), - query.state.clone(), - AuthorizationType::AuthorizationCode, - ) - .await - } - ResponseType::Token => { - // Create authorization - client - .new_pending_authorization( - &database, - query.scope.clone(), - query.state.clone(), - AuthorizationType::Implicit, - ) - .await - } - }; + let pending_authorization = client + .new_pending_authorization( + &database, + query.scope.clone(), + query.state.clone(), + response_to_authorization_type(&query.response_type), + query.nonce.clone(), + ) + .await; let pending_authorization = match pending_authorization { Ok(pa) => pa, @@ -99,9 +97,17 @@ pub async fn authorize( }; // Redirect to login page - OAuth2AuthorizationResponse::Ok(Redirect::new(format!( + OAuth2AuthorizationResponse::Ok(Uncached::new(Redirect::new(format!( "{}?authorization={}", config.http.ui_login_path, pending_authorization.id(), - ))) + )))) +} + +fn response_to_authorization_type(rt: &ResponseType) -> AuthorizationType { + match rt { + ResponseType::Code => AuthorizationType::AuthorizationCode, + ResponseType::Token => AuthorizationType::Implicit, + ResponseType::IdToken => AuthorizationType::IdToken, + } } diff --git a/server/wilford/src/routes/oauth/introspect.rs b/server/wilford/src/routes/oauth/introspect.rs index c360f33..67a82c1 100644 --- a/server/wilford/src/routes/oauth/introspect.rs +++ b/server/wilford/src/routes/oauth/introspect.rs @@ -88,7 +88,7 @@ pub async fn introspect( } } - let user = User::get_by_id(&database, &token.espo_user_id) + let user = User::get_by_id(&database, &token.user_id) .await? .ok_or(IntrospectError::Internal)?; @@ -101,6 +101,6 @@ pub async fn introspect( exp: token.expires_at, iat: token.issued_at, nbf: token.issued_at, - sub: token.espo_user_id, + sub: token.user_id, })) } diff --git a/server/wilford/src/routes/oauth/token.rs b/server/wilford/src/routes/oauth/token.rs index c2dee54..ebbbb81 100644 --- a/server/wilford/src/routes/oauth/token.rs +++ b/server/wilford/src/routes/oauth/token.rs @@ -1,9 +1,13 @@ -use crate::routes::appdata::WDatabase; +use crate::response_types::Uncached; +use crate::routes::appdata::{WConfig, WDatabase}; use crate::routes::oauth::OAuth2ErrorKind; -use actix_web::cookie::time; +use crate::routes::WOidcSigningKey; use actix_web::cookie::time::OffsetDateTime; use actix_web::web; -use database::oauth2_client::{OAuth2AuthorizationCode, OAuth2Client, RefreshToken}; +use database::oauth2_client::{ + create_id_token, JwtSigningAlgorithm, OAuth2AuthorizationCode, OAuth2Client, RefreshToken, +}; +use database::user::User; use serde::{Deserialize, Serialize}; use tap::TapFallible; use tracing::warn; @@ -33,12 +37,15 @@ pub struct Response { expires_in: i64, refresh_token: String, scope: String, + id_token: String, } pub async fn token( database: WDatabase, form: web::Form
, -) -> Result, OAuth2ErrorKind> { + config: WConfig, + oidc_signing_key: WOidcSigningKey, +) -> Result>, OAuth2ErrorKind> { let client = OAuth2Client::get_by_client_id(&database, &form.client_id) .await .tap_err(|e| warn!("{e}")) @@ -74,19 +81,34 @@ pub async fn token( return Err(OAuth2ErrorKind::InvalidGrant); } + let authorization_nonce = authorization.nonce.clone(); + let (atoken, rtoken) = client .new_token_pair(&database, authorization) .await .tap_err(|e| warn!("{e}")) .map_err(|_| OAuth2ErrorKind::ServerError)?; - Ok(web::Json(Response { + Ok(Uncached::new(web::Json(Response { + id_token: create_id_token( + config.oidc_issuer.clone(), + &client, + &User::get_by_id(&database, &rtoken.user_id) + .await + .map_err(|_| OAuth2ErrorKind::ServerError)? + .ok_or(OAuth2ErrorKind::ServerError)?, + &oidc_signing_key.0, + &atoken, + authorization_nonce, + JwtSigningAlgorithm::RS256, + ) + .map_err(|_| OAuth2ErrorKind::ServerError)?, access_token: atoken.token, token_type: "bearer".to_string(), scope: atoken.scopes.unwrap_or_default(), - expires_in: time::OffsetDateTime::now_utc().unix_timestamp() - atoken.expires_at, + expires_in: OffsetDateTime::now_utc().unix_timestamp() - atoken.expires_at, refresh_token: rtoken.token, - })) + }))) } GrantType::RefreshToken => { let rtoken = match &form.refresh_token { @@ -110,13 +132,26 @@ pub async fn token( .tap_err(|e| warn!("{e}")) .map_err(|_| OAuth2ErrorKind::ServerError)?; - Ok(web::Json(Response { + Ok(Uncached::new(web::Json(Response { + id_token: create_id_token( + config.oidc_issuer.clone(), + &client, + &User::get_by_id(&database, &rtoken.user_id) + .await + .map_err(|_| OAuth2ErrorKind::ServerError)? + .ok_or(OAuth2ErrorKind::ServerError)?, + &oidc_signing_key.0, + &atoken, + None, + JwtSigningAlgorithm::RS256, + ) + .map_err(|_| OAuth2ErrorKind::ServerError)?, access_token: atoken.token, token_type: "bearer".to_string(), expires_in: atoken.expires_at - OffsetDateTime::now_utc().unix_timestamp(), scope: atoken.scopes.unwrap_or_default(), refresh_token: rtoken.token, - })) + }))) } } } diff --git a/server/wilford/src/routes/v1/auth/authorization_info.rs b/server/wilford/src/routes/v1/auth/authorization_info.rs index 0eb7699..b0c21ce 100644 --- a/server/wilford/src/routes/v1/auth/authorization_info.rs +++ b/server/wilford/src/routes/v1/auth/authorization_info.rs @@ -1,9 +1,11 @@ -use crate::routes::appdata::WDatabase; -use crate::routes::error::{WebError, WebResult}; use actix_web::web; -use database::oauth2_client::{OAuth2Client, OAuth2PendingAuthorization}; use serde::{Deserialize, Serialize}; +use database::oauth2_client::{OAuth2Client, OAuth2PendingAuthorization}; + +use crate::routes::appdata::WDatabase; +use crate::routes::error::{WebErrorKind, WebResult}; + #[derive(Deserialize)] pub struct Query { authorization: String, @@ -21,16 +23,18 @@ pub async fn authorization_info( ) -> WebResult> { let authorization = OAuth2PendingAuthorization::get_by_id(&database, &query.authorization) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; match &authorization { - OAuth2PendingAuthorization::EspoAuthorized(_) => {} - OAuth2PendingAuthorization::EspoUnauthorized(_) => return Err(WebError::Unauthorized), + OAuth2PendingAuthorization::Authorized(_) => {} + OAuth2PendingAuthorization::Unauthorized(_) => { + return Err(WebErrorKind::Unauthorized.into()) + } } let client = OAuth2Client::get_by_client_id(&database, &authorization.client_id()) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; Ok(web::Json(Response { client_name: client.name, diff --git a/server/wilford/src/routes/v1/auth/authorize.rs b/server/wilford/src/routes/v1/auth/authorize.rs index 9c4b5b9..d04f6af 100644 --- a/server/wilford/src/routes/v1/auth/authorize.rs +++ b/server/wilford/src/routes/v1/auth/authorize.rs @@ -1,14 +1,21 @@ -use crate::routes::appdata::WDatabase; -use crate::routes::error::{WebError, WebResult}; -use crate::routes::oauth::{OAuth2AuthorizationResponse, OAuth2Error, OAuth2ErrorKind}; -use crate::routes::redirect::Redirect; use actix_web::cookie::time::OffsetDateTime; use actix_web::web; +use database::driver::Database; +use serde::{Deserialize, Serialize}; +use tap::TapFallible; +use tracing::{instrument, warn}; + use database::oauth2_client::{ - AuthorizationType, OAuth2AuthorizationCodeCreationError, OAuth2Client, - OAuth2PendingAuthorization, + create_id_token, AccessToken, AuthorizationType, JwtSigningAlgorithm, + OAuth2AuthorizationCodeCreationError, OAuth2Client, OAuth2PendingAuthorization, }; -use serde::{Deserialize, Serialize}; +use database::user::User; + +use crate::response_types::Redirect; +use crate::routes::appdata::{WConfig, WDatabase}; +use crate::routes::error::{WebErrorKind, WebResult}; +use crate::routes::oauth::{OAuth2AuthorizationResponse, OAuth2Error, OAuth2ErrorKind}; +use crate::routes::WOidcSigningKey; #[derive(Deserialize)] pub struct Query { @@ -16,18 +23,21 @@ pub struct Query { grant: bool, } +#[instrument(skip_all)] pub async fn authorize( database: WDatabase, + oidc_signing_key: WOidcSigningKey, + config: WConfig, query: web::Query, ) -> WebResult> { let pending_authorization = OAuth2PendingAuthorization::get_by_id(&database, &query.authorization) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; let client = OAuth2Client::get_by_client_id(&database, &pending_authorization.client_id()) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; if !query.grant { return Ok(OAuth2AuthorizationResponse::Err(OAuth2Error::new( @@ -44,9 +54,9 @@ pub async fn authorize( .new_authorization_code(&database, pending_authorization) .await .map_err(|e| match e { - OAuth2AuthorizationCodeCreationError::Sqlx(e) => WebError::Database(e), + OAuth2AuthorizationCodeCreationError::Sqlx(e) => WebErrorKind::Database(e), OAuth2AuthorizationCodeCreationError::Unauthorized => { - WebError::InvalidInternalState + WebErrorKind::InvalidInternalState } })?; @@ -68,39 +78,89 @@ pub async fn authorize( ) } AuthorizationType::Implicit => { - let access_token = client - .new_access_token(&database, pending_authorization) - .await - .map_err(|e| match e { - OAuth2AuthorizationCodeCreationError::Sqlx(e) => WebError::Database(e), - OAuth2AuthorizationCodeCreationError::Unauthorized => { - WebError::InvalidInternalState - } - })?; + let access_token = new_access_token(&client, pending_authorization, &database).await?; - #[derive(Serialize)] - struct RedirectFragment { - access_token: String, - #[serde(skip_serializing_if = "Option::is_none")] - state: Option, - token_type: &'static str, - expires_in: i64, - } + format!( + "{}#{}", + client.redirect_uri, + create_implicit_fragment(None, access_token, state), + ) + } + AuthorizationType::IdToken => { + let nonce = pending_authorization.nonce().clone(); + let access_token = new_access_token(&client, pending_authorization, &database).await?; format!( "{}#{}", client.redirect_uri, - serde_qs::to_string(&RedirectFragment { - access_token: access_token.token, - token_type: "bearer", - expires_in: access_token.expires_at - - OffsetDateTime::now_utc().unix_timestamp(), - state, - }) - .expect("Serializing query string"), + create_implicit_fragment( + Some( + create_id_token( + config.oidc_issuer.clone(), + &client, + &User::get_by_id(&database, &access_token.user_id) + .await? + .ok_or(WebErrorKind::InternalServerError)?, + &oidc_signing_key.0, + &access_token, + nonce, + JwtSigningAlgorithm::RS256, + ) + .tap_err(|e| warn!("Failed to create ID token: {e}")) + .map_err(|_| WebErrorKind::InternalServerError)? + ), + access_token, + state + ) ) } }; Ok(OAuth2AuthorizationResponse::Ok(Redirect::new(redirect_uri))) } + +/// Create a new OAuth2 access token. +/// Useful for the OAuth2 Implicit flow and the OpenID Connect IdToken flow. +async fn new_access_token( + client: &OAuth2Client, + pending_authorization: OAuth2PendingAuthorization, + database: &Database, +) -> WebResult { + Ok(client + .new_access_token(&database, pending_authorization) + .await + .map_err(|e| match e { + OAuth2AuthorizationCodeCreationError::Sqlx(e) => WebErrorKind::Database(e), + OAuth2AuthorizationCodeCreationError::Unauthorized => { + WebErrorKind::InvalidInternalState + } + })?) +} + +#[derive(Serialize)] +struct RedirectFragment { + access_token: String, + #[serde(skip_serializing_if = "Option::is_none")] + state: Option, + token_type: &'static str, + expires_in: i64, + #[serde(skip_serializing_if = "Option::is_none")] + id_token: Option, +} + +/// Create the fragment string used for the OAuth2 implicit flow and the OpenID Connect IdToken flow. +/// The `id_token` string should only be supplied for the OpenID Connect IdToken flow. +fn create_implicit_fragment( + id_token: Option, + access_token: AccessToken, + state: Option, +) -> String { + serde_qs::to_string(&RedirectFragment { + access_token: access_token.token, + token_type: "bearer", + expires_in: access_token.expires_at - OffsetDateTime::now_utc().unix_timestamp(), + state, + id_token, + }) + .expect("Serializing query string") +} diff --git a/server/wilford/src/routes/v1/auth/login.rs b/server/wilford/src/routes/v1/auth/login.rs index f7d4a24..92d9b24 100644 --- a/server/wilford/src/routes/v1/auth/login.rs +++ b/server/wilford/src/routes/v1/auth/login.rs @@ -1,13 +1,14 @@ use crate::espo::user::{EspoUser, LoginStatus}; use crate::routes::appdata::{WConfig, WDatabase, WEspo}; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use actix_web::web; use database::oauth2_client::OAuth2PendingAuthorization; use database::user::User; use serde::{Deserialize, Serialize}; use std::collections::HashSet; +use tracing::instrument; -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] pub struct Request { authorization: String, username: String, @@ -21,6 +22,7 @@ pub struct Response { totp_required: bool, } +#[instrument(skip_all)] pub async fn login( database: WDatabase, config: WConfig, @@ -29,7 +31,7 @@ pub async fn login( ) -> WebResult> { let authorization = OAuth2PendingAuthorization::get_by_id(&database, &payload.authorization) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; let login = EspoUser::try_login( &config.espo.host, @@ -38,7 +40,7 @@ pub async fn login( payload.totp_code.as_deref(), ) .await - .map_err(|e| WebError::Espo(e))?; + .map_err(|e| WebErrorKind::Espo(e))?; // OAuth2 defines `scope` to be all scopes, seperated by a ' ' (space char) // Where duplicates can be ignored. @@ -55,7 +57,7 @@ pub async fn login( // Only exceptions to this are Espo admins, they may have all scopes, // and the OIDC scopes match User::get_by_id(&database, &id).await? { - Some(user) if user.is_espo_admin => {} + Some(user) if user.is_admin => {} Some(user) => { let permitted_scopes = HashSet::from_iter(user.list_permitted_scopes(&database).await?); @@ -71,40 +73,41 @@ pub async fn login( .collect::>(); if !disallowed_scopes.is_empty() { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } } None => { let espo_user = EspoUser::get_by_id(&espo, &id) .await - .map_err(|e| WebError::Espo(e))?; + .map_err(|e| WebErrorKind::Espo(e))?; let user = User::new( &database, id.clone(), espo_user.name, + espo_user.email_address, espo_user.user_type.eq("admin"), ) .await?; // No permitted scopes are granted yet - if !user.is_espo_admin { + if !user.is_admin { // Remove the OIDC scopes let oidc_scopes = oidc_scopes(); let disallowed_scopes = scope_set.difference(&oidc_scopes).collect::>(); if !disallowed_scopes.is_empty() { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } } } } authorization - .set_espo_user_id(&database, &id) + .set_user_id(&database, &id) .await - .map_err(|_| WebError::BadRequest)?; + .map_err(|_| WebErrorKind::BadRequest)?; Ok(web::Json(Response { status: true, diff --git a/server/wilford/src/routes/v1/cat/add.rs b/server/wilford/src/routes/v1/cat/add.rs index 15ed3ae..2b0f30c 100644 --- a/server/wilford/src/routes/v1/cat/add.rs +++ b/server/wilford/src/routes/v1/cat/add.rs @@ -1,11 +1,13 @@ +use actix_web::web; +use serde::Deserialize; + +use database::constant_access_tokens::ConstantAccessToken; + +use crate::response_types::Empty; use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::empty::Empty; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::constant_access_tokens::ConstantAccessToken; -use serde::Deserialize; #[derive(Deserialize)] pub struct Request { @@ -14,7 +16,7 @@ pub struct Request { pub async fn add(database: WDatabase, auth: Auth, payload: web::Json) -> WebResult { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let exists = ConstantAccessToken::list(&database) @@ -24,11 +26,11 @@ pub async fn add(database: WDatabase, auth: Auth, payload: web::Json) - .is_some(); if exists { - return Err(WebError::BadRequest); + return Err(WebErrorKind::BadRequest.into()); } if payload.name.len() > 64 { - return Err(WebError::BadRequest); + return Err(WebErrorKind::BadRequest.into()); } ConstantAccessToken::new(&database, payload.name.clone()).await?; diff --git a/server/wilford/src/routes/v1/cat/list.rs b/server/wilford/src/routes/v1/cat/list.rs index 54f65bf..d637318 100644 --- a/server/wilford/src/routes/v1/cat/list.rs +++ b/server/wilford/src/routes/v1/cat/list.rs @@ -1,10 +1,12 @@ +use actix_web::web; +use serde::Serialize; + +use database::constant_access_tokens::ConstantAccessToken; + use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::constant_access_tokens::ConstantAccessToken; -use serde::Serialize; #[derive(Serialize)] pub struct Response { @@ -19,7 +21,7 @@ pub struct Cat { pub async fn list(database: WDatabase, auth: Auth) -> WebResult> { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let tokens = ConstantAccessToken::list(&database) diff --git a/server/wilford/src/routes/v1/cat/remove.rs b/server/wilford/src/routes/v1/cat/remove.rs index f30cb30..553f127 100644 --- a/server/wilford/src/routes/v1/cat/remove.rs +++ b/server/wilford/src/routes/v1/cat/remove.rs @@ -1,11 +1,13 @@ +use actix_web::web; +use serde::Deserialize; + +use database::constant_access_tokens::ConstantAccessToken; + +use crate::response_types::Empty; use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::empty::Empty; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::constant_access_tokens::ConstantAccessToken; -use serde::Deserialize; #[derive(Deserialize)] pub struct Request { @@ -18,12 +20,12 @@ pub async fn remove( payload: web::Json, ) -> WebResult { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let cat = ConstantAccessToken::get_by_token(&database, &payload.token) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; cat.revoke(&database).await?; Ok(Empty) diff --git a/server/wilford/src/routes/v1/clients/add.rs b/server/wilford/src/routes/v1/clients/add.rs index 0140df5..1dc5044 100644 --- a/server/wilford/src/routes/v1/clients/add.rs +++ b/server/wilford/src/routes/v1/clients/add.rs @@ -1,11 +1,13 @@ +use actix_web::web; +use serde::Deserialize; + +use database::oauth2_client::OAuth2Client; + +use crate::response_types::Empty; use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::empty::Empty; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::oauth2_client::OAuth2Client; -use serde::Deserialize; #[derive(Deserialize)] pub struct Request { @@ -15,11 +17,11 @@ pub struct Request { pub async fn add(database: WDatabase, auth: Auth, payload: web::Json) -> WebResult { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } if payload.name.len() > 64 { - return Err(WebError::BadRequest); + return Err(WebErrorKind::BadRequest.into()); } let exists = OAuth2Client::list(&database) @@ -29,7 +31,7 @@ pub async fn add(database: WDatabase, auth: Auth, payload: web::Json) - .is_some(); if exists { - return Err(WebError::BadRequest); + return Err(WebErrorKind::BadRequest.into()); } OAuth2Client::new( diff --git a/server/wilford/src/routes/v1/clients/internal.rs b/server/wilford/src/routes/v1/clients/internal.rs index 6e9093b..0d21f82 100644 --- a/server/wilford/src/routes/v1/clients/internal.rs +++ b/server/wilford/src/routes/v1/clients/internal.rs @@ -1,9 +1,11 @@ -use crate::routes::appdata::WDatabase; -use crate::routes::error::{WebError, WebResult}; use actix_web::web; -use database::oauth2_client::OAuth2Client; use serde::Serialize; +use database::oauth2_client::OAuth2Client; + +use crate::routes::appdata::WDatabase; +use crate::routes::error::{WebErrorKind, WebResult}; + #[derive(Serialize)] pub struct Response { name: String, @@ -17,7 +19,7 @@ pub async fn internal(database: WDatabase) -> WebResult> { .await? .into_iter() .find(|c| c.is_internal) - .ok_or(WebError::InvalidInternalState)?; + .ok_or(WebErrorKind::InvalidInternalState)?; Ok(web::Json(Response { name: client.name, diff --git a/server/wilford/src/routes/v1/clients/list.rs b/server/wilford/src/routes/v1/clients/list.rs index ce02729..27e3a6a 100644 --- a/server/wilford/src/routes/v1/clients/list.rs +++ b/server/wilford/src/routes/v1/clients/list.rs @@ -1,10 +1,12 @@ +use actix_web::web; +use serde::Serialize; + +use database::oauth2_client::OAuth2Client; + use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::oauth2_client::OAuth2Client; -use serde::Serialize; #[derive(Serialize)] pub struct Response { @@ -21,7 +23,7 @@ pub struct Client { pub async fn list(database: WDatabase, auth: Auth) -> WebResult> { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let clients = OAuth2Client::list(&database) diff --git a/server/wilford/src/routes/v1/clients/remove.rs b/server/wilford/src/routes/v1/clients/remove.rs index e9dbec6..d31808f 100644 --- a/server/wilford/src/routes/v1/clients/remove.rs +++ b/server/wilford/src/routes/v1/clients/remove.rs @@ -1,11 +1,13 @@ +use actix_web::web; +use serde::Deserialize; + +use database::oauth2_client::OAuth2Client; + +use crate::response_types::Empty; use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::empty::Empty; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::oauth2_client::OAuth2Client; -use serde::Deserialize; #[derive(Deserialize)] pub struct Request { @@ -18,12 +20,12 @@ pub async fn remove( payload: web::Json, ) -> WebResult { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let client = OAuth2Client::get_by_client_id(&database, &payload.client_id) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; client.delete(&database).await?; Ok(Empty) diff --git a/server/wilford/src/routes/v1/user/list.rs b/server/wilford/src/routes/v1/user/list.rs index 0726d2a..5b02d41 100644 --- a/server/wilford/src/routes/v1/user/list.rs +++ b/server/wilford/src/routes/v1/user/list.rs @@ -1,9 +1,10 @@ +use actix_web::web; +use serde::Serialize; + use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use serde::Serialize; #[derive(Serialize)] pub struct Response { @@ -19,7 +20,7 @@ pub struct User { pub async fn list(database: WDatabase, auth: Auth) -> WebResult> { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let users = database::user::User::list(&database) @@ -27,8 +28,8 @@ pub async fn list(database: WDatabase, auth: Auth) -> WebResult>(); diff --git a/server/wilford/src/routes/v1/user/permitted_scopes/add.rs b/server/wilford/src/routes/v1/user/permitted_scopes/add.rs index 6aa09fc..ba2d649 100644 --- a/server/wilford/src/routes/v1/user/permitted_scopes/add.rs +++ b/server/wilford/src/routes/v1/user/permitted_scopes/add.rs @@ -1,11 +1,13 @@ +use actix_web::web; +use serde::Deserialize; + +use database::user::User; + +use crate::response_types::Empty; use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::empty::Empty; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::user::User; -use serde::Deserialize; #[derive(Deserialize)] pub struct Payload { @@ -17,16 +19,16 @@ pub struct Payload { pub async fn add(database: WDatabase, auth: Auth, payload: web::Json) -> WebResult { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let user = User::get_by_id(&database, &payload.to) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; let current_scopes = user.list_permitted_scopes(&database).await?; if current_scopes.contains(&payload.scope) { - return Err(WebError::BadRequest); + return Err(WebErrorKind::BadRequest.into()); } user.grant_permitted_scope(&database, &payload.scope) diff --git a/server/wilford/src/routes/v1/user/permitted_scopes/list.rs b/server/wilford/src/routes/v1/user/permitted_scopes/list.rs index 5aaf5c4..1e22a29 100644 --- a/server/wilford/src/routes/v1/user/permitted_scopes/list.rs +++ b/server/wilford/src/routes/v1/user/permitted_scopes/list.rs @@ -1,10 +1,12 @@ +use actix_web::web; +use serde::{Deserialize, Serialize}; + +use database::user::User; + use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::user::User; -use serde::{Deserialize, Serialize}; #[derive(Serialize)] pub struct Response { @@ -23,12 +25,12 @@ pub async fn list( query: web::Query, ) -> WebResult> { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let user = User::get_by_id(&database, &query.user) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; let scopes = user.list_permitted_scopes(&database).await?; Ok(web::Json(Response { scopes })) diff --git a/server/wilford/src/routes/v1/user/permitted_scopes/remove.rs b/server/wilford/src/routes/v1/user/permitted_scopes/remove.rs index 663c8e8..83a3f08 100644 --- a/server/wilford/src/routes/v1/user/permitted_scopes/remove.rs +++ b/server/wilford/src/routes/v1/user/permitted_scopes/remove.rs @@ -1,11 +1,13 @@ +use actix_web::web; +use serde::Deserialize; + +use database::user::User; + +use crate::response_types::Empty; use crate::routes::appdata::WDatabase; use crate::routes::auth::Auth; -use crate::routes::empty::Empty; -use crate::routes::error::{WebError, WebResult}; +use crate::routes::error::{WebErrorKind, WebResult}; use crate::routes::v1::MANAGE_SCOPE; -use actix_web::web; -use database::user::User; -use serde::Deserialize; #[derive(Deserialize)] pub struct Request { @@ -21,16 +23,16 @@ pub async fn remove( payload: web::Json, ) -> WebResult { if !auth.has_scope(MANAGE_SCOPE) { - return Err(WebError::Forbidden); + return Err(WebErrorKind::Forbidden.into()); } let user = User::get_by_id(&database, &payload.from) .await? - .ok_or(WebError::NotFound)?; + .ok_or(WebErrorKind::NotFound)?; let current_scops = user.list_permitted_scopes(&database).await?; if !current_scops.contains(&payload.scope) { - return Err(WebError::NotFound); + return Err(WebErrorKind::NotFound.into()); } user.remove_permitted_scope(&database, &payload.scope) diff --git a/server/wilford/src/routes/well_known/jwks.rs b/server/wilford/src/routes/well_known/jwks.rs new file mode 100644 index 0000000..45e1ad9 --- /dev/null +++ b/server/wilford/src/routes/well_known/jwks.rs @@ -0,0 +1,55 @@ +use actix_web::web; +use base64::prelude::BASE64_URL_SAFE_NO_PAD; +use base64::Engine; +use rsa::pkcs8::DecodePublicKey; +use rsa::traits::PublicKeyParts; +use rsa::{BigUint, RsaPublicKey}; +use serde::{Serialize, Serializer}; + +use crate::routes::appdata::WOidcPublicKey; +use crate::routes::error::WebResult; + +#[derive(Serialize)] +pub struct Jwks { + keys: Vec, +} + +#[derive(Serialize)] +pub struct Key { + kty: String, + r#use: String, + alg: String, + kid: String, + key_ops: Vec, + // k: String, + #[serde(serialize_with = "serialize")] + n: BigUint, + #[serde(serialize_with = "serialize")] + e: BigUint, +} + +pub async fn jwks(oidc_public_key: WOidcPublicKey) -> WebResult> { + let public_key = RsaPublicKey::from_public_key_pem(&oidc_public_key.0)?; + + Ok(web::Json(Jwks { + keys: vec![Key { + kty: "RSA".to_string(), + r#use: "sig".to_string(), + alg: "RS256".to_string(), + kid: "rsa".to_string(), // We dont have a kid + key_ops: vec!["verify".to_string()], + // k: (**oidc_public_key).0.clone(), + n: public_key.n().clone(), + e: public_key.e().clone(), + }], + })) +} + +pub fn serialize(value: &BigUint, serializer: S) -> Result +where + S: Serializer, +{ + let bytes = value.to_bytes_be(); + let base64 = BASE64_URL_SAFE_NO_PAD.encode(bytes.as_slice()); + serializer.serialize_str(&base64) +} diff --git a/server/wilford/src/routes/well_known/mod.rs b/server/wilford/src/routes/well_known/mod.rs new file mode 100644 index 0000000..5be1274 --- /dev/null +++ b/server/wilford/src/routes/well_known/mod.rs @@ -0,0 +1,21 @@ +mod jwks; +mod openid_configuration; + +use actix_route_config::Routable; +use actix_web::web; +use actix_web::web::ServiceConfig; + +pub struct Router; + +impl Routable for Router { + fn configure(config: &mut ServiceConfig) { + config.service( + web::scope("/.well-known") + .route( + "/openid-configuration", + web::get().to(openid_configuration::openid_configuration), + ) + .route("/jwks.json", web::get().to(jwks::jwks)), + ); + } +} diff --git a/server/wilford/src/routes/well_known/openid_configuration.rs b/server/wilford/src/routes/well_known/openid_configuration.rs new file mode 100644 index 0000000..36fa282 --- /dev/null +++ b/server/wilford/src/routes/well_known/openid_configuration.rs @@ -0,0 +1,30 @@ +use crate::routes::appdata::WConfig; +use actix_web::web; +use serde::Serialize; + +#[derive(Serialize)] +pub struct OpenidConfiguration { + issuer: String, + authorization_endpoint: String, + token_endpoint: String, + response_types_supported: Vec, + grant_types_supported: Vec, + id_token_signing_alg_values_supported: Vec, + jwks_uri: String, +} + +pub async fn openid_configuration(config: WConfig) -> web::Json { + web::Json(OpenidConfiguration { + issuer: config.oidc_issuer.clone(), + authorization_endpoint: config.http.authorization_endpoint.clone(), + token_endpoint: config.http.token_endpoint.clone(), + response_types_supported: vec![ + "code".to_string(), + "id_token token".to_string(), + "token".to_string(), + ], + grant_types_supported: vec!["authorization_code".to_string(), "implicit".to_string()], + id_token_signing_alg_values_supported: vec!["RS256".to_string()], + jwks_uri: config.http.jwks_uri_endpoint.clone(), + }) +} diff --git a/test_oidc_key.pem b/test_oidc_key.pem new file mode 100644 index 0000000..17ba90d --- /dev/null +++ b/test_oidc_key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCYcP5GIqGKZZb1 +OmOvr+VS1s2Z0DSlAX+iJ/ghkYQrMFdN1DZhIurz9uqLRM7T/MH8Z1CEIyf0m4fg +ek9Nr65onUY4S+jwQhYHQekokKuC5A/fHnd4NFT+Ys/NOpDaOzhgejMIUmKl1nut +5vzczy+Uy8Fhw1bohk7wOm+Q2mEzjeqWrLKLIVlUlpv7SMYWmLv4hXDJuOXfUtB0 +HXfJw/NasZsoNNb06s6Xw/8AjM0zUfTmXNnUYp2Yg5gHZsOZ1VutGDfVVVTxKPs1 +Yi2Q394h4LYItR/dGel5DbVmWSD3hVkCFsQQSbqdaPQlSM4H/mkfJs5lggzjxMZg +IbCgXKBmHpPfAb0KZv0TBkVBNYLQXGRbOoWUlTpCtJTZZ46eA200TGiNj7ou6DfK +oI/y5g+WTp8ZD/XYo5R6vDLEe6g2VNZ6s9bchbBqaOo72LxdaxInxrjI9F0j89gx +nPdudQbTNGCmQk0B/QHWwJCXosHJJp2kYlP+DSGsrR/9lhffdFuVjztZ/6P+Q0P+ +iPnBaXTqTk15qp8+AsHhQznCAX0WYSQGxMKMakrxFu9gc/SPOkrB7WZuIRL8IYwV +Ls8OypflfBG6URe91UFeWWU9wAPqCbj3yGL/St8S71I2kKhDJT56XCCDjQeHxwba +02AwB1flimAvgZmMOhD78Sgv/40zVQIDAQABAoICAADa3dI1/NgrMbCoLWYPSnwv +kDiFEDEwRdoW90v1WBRrQzWkFwvDh7QHdgoteQ8J+MAaFgLZfBIarvqgNPtujpUv +nmaIpnR2Dfp2OCum9dZmiQrrTbZy31q2xdKxSA+U0sc7S8X5ghbLIEPIFaPa8g8l +zIIS97h6kwXfqniEK7EqU+0nEszUEbgwLH7j2EM62PMIweNm2S3cWXxGHgj7NT8U +hiR3lZ7SBdzRXs75iWe/1XoBttxe9o0VwaFo/wYQEShyeeE+MbfDMqHsPF72vubZ +vR1lm/0HPRBq9mAmy180dEA6v5FrX4yPya6/Z4+EzjMDanskmAXpqmMnooygIwft +X16an4ECMcsgYTHBGRGEQLMZGUWxwuvgDUlNmN0ColbNHBhObopzU8qG4isZAePh +LPU+/YJgpLupQ4V6V5cDyqLBoeio3FtliJd7a8mHMLicUUxlhPAeGjoATcKg0s+N +KACOzUGMtGLcGi5G47/yqWSDfz9Bdw2cMqGKx+GcU7/iVQ9RL0D4k6IOHj7CRyw1 +WWJrkKY6KTRG6H4o94Ctr0BpG4R7GviEo82ApF/0jYXCaH2BC60pkPKnkFv/ptfV +ZA4mDJCAQRRu/aIjavgLriFj6lV6GjTsTX+mt3cz+8qrSAUAVp+LOcGFf7I6UrIA +6pIDmWypez4Qb+FrtfcBAoIBAQDGrDb1OUoFvU2XJ/RoxblZsR1fpZnWriSQk8j1 +Lf+GX0zqnsCG1x++Fe+QyuGeb89Sd+HreGPtHBz2dOw/Uf2VXgfVNaGu59JiFfHV +i/tLssdFzINRSs89UBBm8WPTY7t/KzxbPFog90p5b27hGaDeq07Q3t2jAMe6ZLEO +o6Ii66eE84Oz+2B7dQNdP+grW2FKWIsF6L9yOZyRNwNjJOxmzVU3aJaFNyBVgBa1 +zvd30M8oRHdIFpCYeBQ+FM+b1WjMhJsGHvGNnDUx/IDwv33pcETRaNwq24M55pEN +QpjKE8azCogmnTRAkoUn5+4xiYdISB72zZL9iOktglo17YjRAoIBAQDEbbWFAp10 +qBlbY6FCQosZ67kK6D/UGMDAcqeqwT2jlRt2DxVCB0gPMAq6KJoOmPftBVXTBBZY +/FVDD4OrCDKtmuRJ5Atn3/DKKkGlSMvF5J+MSPMw2IrvGE8eqetsoUij4AWDDe89 +IXLdKCtqf8vnDTh4PagfKAn/QVySOcVgRuv4mQd/uu0k32lj2vqekrIhzXhaI8Gj +MSJBud/2VmYiApVxO3JdkNPQEdvZ7+8GyXDOZm+92hHUEV+d1crIfER+aOpxA7ao ++1tAl1ALKxpqm9edZiFF/+gKaJVEPpAeRBhKqDQbw953AAqodGxioPIvbj8C/BTt ++MHifJvgVuNFAoIBAFC5hvcrGQWt/ontpSSxOnfUPJAxlImjZp4MPnpI/2uU64bl +jGr9nBs7hIT7uqMK4V2r8nMgDtI4Oxh1N80evn/jniEm0pRkA5rxObtATpJaiRe2 +WqE8WWqbTp7VdG9fNb8dTT/xDNoaQPuIqK/0HuZ7CuJZvArQdVcqNLszB91SjVW7 +MIeAG5ruV7ibD/YWAjkYMxzqjmeWOyTSCqFNTnUS9441A/wLAytaiV/EQc9AW/wL +nb//0y7wk6EKCbVTCGsZcngROKsGjqLJ64Zm+H1SrdO83MDZQr/TDJsrtTNex7zW +Ca5O7/1Qy6QEXEdqjzpye4piy1gTOlPQhGEnLBECggEAGBwPJ6BGi0cGtbzo0iUw +xaG1OK2BCBtxrS1lfwPw+N8YW8lGJrCWG6cd6fOJ1TjZVXJOMnRR+eIe5PhGhPug +nMZiNt+SttE50NaW8B6bRFLncmSR1PD/PJGTRIQ+rTqum86nXoqbTrmIS7Tef1fo +QxfQUpHezNQxRe9T2W7V1xddZ0oBLaBX8yqo2OTL1iLTUmrR1t6Eqe8h8UdLQgUg +zBPdG+TcCLf6B5PplDpq8D5RZkQUew0FGy52ufy/wxPdpFZkUDLQfLU9YhQwcu57 +c5JlSKB8fQcICB8GEzZKoixhfuheXPoRmDGdHUgM9Z4jm8bAXTFrsDnUMmRxDXwc +sQKCAQEArwQZbfvhoqUCvbDbpnf3qifYjNGfodUMZpkEaOF0i+F2yMozgWZMZBck +bgF+y9HXuPSLUEWqmfcmDQncHJaGJ6J3dn14eBs4AHStIROIdJ3W/JnXLik9XX9g +gwZAYmR58pFbE8vokYefWK+WBRNyp2do+B0L4arStfU0mJbM24VOakKNOOXtTJSm +5Vqs1r+GsIhXVUunllNdOkOIN7+BZjEUJWUODWi/Y9xGYCxTjWMBmUYeXGFUHBu3 +c64uPlQt4pv+qGxn+PICXFiU1WiASLDemA/UWwDznLaaoEp/5kvKQCSjj9W3X/Xa +gZn/+8gFf14XcyWxmqj+55AyOfsRsw== +-----END PRIVATE KEY----- diff --git a/test_oidc_key.pem.pub b/test_oidc_key.pem.pub new file mode 100644 index 0000000..59c5bb0 --- /dev/null +++ b/test_oidc_key.pem.pub @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmHD+RiKhimWW9Tpjr6/l +UtbNmdA0pQF/oif4IZGEKzBXTdQ2YSLq8/bqi0TO0/zB/GdQhCMn9JuH4HpPTa+u +aJ1GOEvo8EIWB0HpKJCrguQP3x53eDRU/mLPzTqQ2js4YHozCFJipdZ7reb83M8v +lMvBYcNW6IZO8DpvkNphM43qlqyyiyFZVJab+0jGFpi7+IVwybjl31LQdB13ycPz +WrGbKDTW9OrOl8P/AIzNM1H05lzZ1GKdmIOYB2bDmdVbrRg31VVU8Sj7NWItkN/e +IeC2CLUf3RnpeQ21Zlkg94VZAhbEEEm6nWj0JUjOB/5pHybOZYIM48TGYCGwoFyg +Zh6T3wG9Cmb9EwZFQTWC0FxkWzqFlJU6QrSU2WeOngNtNExojY+6Lug3yqCP8uYP +lk6fGQ/12KOUerwyxHuoNlTWerPW3IWwamjqO9i8XWsSJ8a4yPRdI/PYMZz3bnUG +0zRgpkJNAf0B1sCQl6LBySadpGJT/g0hrK0f/ZYX33RblY87Wf+j/kND/oj5wWl0 +6k5NeaqfPgLB4UM5wgF9FmEkBsTCjGpK8RbvYHP0jzpKwe1mbiES/CGMFS7PDsqX +5XwRulEXvdVBXlllPcAD6gm498hi/0rfEu9SNpCoQyU+elwgg40Hh8cG2tNgMAdX +5YpgL4GZjDoQ+/EoL/+NM1UCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/ui/src/main.ts b/ui/src/main.ts index 43ef859..9f0b6dc 100644 --- a/ui/src/main.ts +++ b/ui/src/main.ts @@ -7,10 +7,10 @@ // Plugins import { registerPlugins } from '@/plugins' -export const server = -window.location.host.includes("localhost") ? "http://localhost:2521" // Dev, in docker -: window.location.host.includes("127.0.0.1") ? "http://localhost:8080" // Dev, not docker -: "/wilford"; // Production +const isLocalhost = window.location.host.includes('localhost'); +export const server = isLocalhost + ? "http://localhost:2521" + : "/api"; // Components import App from './App.vue' diff --git a/ui/src/router/index.ts b/ui/src/router/index.ts index 6015f11..6f94cc4 100644 --- a/ui/src/router/index.ts +++ b/ui/src/router/index.ts @@ -52,7 +52,7 @@ const routes = [ ] const router = createRouter({ - history: createWebHistory('/wilford'), + history: createWebHistory(), routes, })