Compare commits

...

194 commits

Author SHA1 Message Date
83d10e1a70
Refactor of micropub request handling
Trying to organise the code better. It now temporarily doesn’t support
update requests. Thought the spec defines them as SHOULD features and
not MUST features. So safe for now :)
2025-04-27 16:38:25 +01:00
23c275945a
Refactor micropub token verification 2025-04-12 11:47:30 +01:00
70f90dd456
Remove un-needed config files 2025-04-10 21:03:22 +01:00
cd5c97afd3
Remove psalm annotations 2025-04-10 20:09:36 +01:00
97f3848b66
Use esbuild 2025-04-10 17:08:19 +01:00
540bd17792
Use lightningcss 2025-04-10 16:53:23 +01:00
1fe9a42d8d
Remove GitHub config 2025-04-07 19:47:48 +01:00
cf978cd749
Fix PasskeysController with new webauthn library version
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2025-04-07 19:44:13 +01:00
126bb29ae2
Laravel Pint fixes
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2025-04-06 17:25:06 +01:00
7a58287b34
Remove references to short domain 2025-04-06 17:22:36 +01:00
328c9badb4
Remove snow fall
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2025-04-06 14:33:45 +01:00
1dfa17abca
Update Laravel to v12
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2025-04-01 21:10:30 +01:00
f2025b801b
Update dependencies
Some checks failed
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
2025-01-25 11:47:43 +00:00
4e7b911fe9
Improve commentt in script
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2025-01-25 11:20:37 +00:00
cf6e65cc03
Improve compress script
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
Generate zst and br files, then  remove any that are actually larger
than the source file
2024-12-28 11:07:48 +00:00
70e5b9bec7
Add snow fall to the site
Some checks failed
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Some tweaks to the node dependencies and compression script also done
2024-11-30 15:58:25 +00:00
84383ecd31
Apply the same fix to replies and reposts
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2024-11-30 15:31:17 +00:00
1d59d57c2e
Fix showing author like on a note 2024-11-30 15:30:07 +00:00
43447ac47b
Fix tests after we corrected the media URLs
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
The media URLs have a path that starts `/storage/`.
2024-10-26 12:52:43 +01:00
d7da42b626
Host images locally
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
We don’t need the complexity of S3. Sepcifically the complexity of
managing my own AWS account, flysystem made the Laravel side easy.

A command is added to copy the the S3 files over to local storage.
2024-10-25 20:40:52 +01:00
d80e8164c8
Update dependencies
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
Further tidy up some config around phpactor and unit testing
2024-10-23 09:16:11 +01:00
37a8e4bba8
Load favicon from a favicon.png file if it exists
Some checks failed
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
This means different people will be able to use their own avatar/favicon
2024-08-23 18:55:08 +01:00
9abe4a6110
Only link to gpg key if it exists in the filesystem
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2024-08-23 18:27:20 +01:00
d77f2302ba
Store requested scopes as a string during IndieAuth flow
Some checks failed
PHP Unit / PHPUnit test suite (pull_request) Has been cancelled
Laravel Pint / Laravel Pint (pull_request) Has been cancelled
2024-08-03 11:33:09 +01:00
dd04921e6e
Merge pull request #1490 from jonnybarnes/dependabot/composer/league/commonmark-2.5.0
build(deps): Bump league/commonmark from 2.4.2 to 2.5.0
2024-07-23 16:48:07 +01:00
dependabot[bot]
71393add2f
build(deps): Bump league/commonmark from 2.4.2 to 2.5.0
Bumps [league/commonmark](https://github.com/thephpleague/commonmark) from 2.4.2 to 2.5.0.
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/2.5/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/2.4.2...2.5.0)

---
updated-dependencies:
- dependency-name: league/commonmark
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-23 15:46:10 +00:00
85aae94496
Merge pull request #1489 from jonnybarnes/dependabot/composer/guzzlehttp/guzzle-7.9.1
build(deps): Bump guzzlehttp/guzzle from 7.9.0 to 7.9.1
2024-07-23 16:44:41 +01:00
dependabot[bot]
4ca14542d7
build(deps): Bump guzzlehttp/guzzle from 7.9.0 to 7.9.1
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.9.0 to 7.9.1.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/7.9/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.9.0...7.9.1)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-23 15:33:41 +00:00
65ca2d3d6c
Merge pull request #1488 from jonnybarnes/dependabot/composer/web-auth/webauthn-lib-5.0.1
build(deps): Bump web-auth/webauthn-lib from 5.0.0 to 5.0.1
2024-07-23 16:33:01 +01:00
dependabot[bot]
3a47f3b33c
build(deps): Bump web-auth/webauthn-lib from 5.0.0 to 5.0.1
Bumps [web-auth/webauthn-lib](https://github.com/web-auth/webauthn-lib) from 5.0.0 to 5.0.1.
- [Commits](https://github.com/web-auth/webauthn-lib/compare/5.0.0...5.0.1)

---
updated-dependencies:
- dependency-name: web-auth/webauthn-lib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-22 09:37:46 +00:00
a93219f4bb
Merge pull request #1487 from jonnybarnes/dependabot/composer/web-auth/webauthn-lib-5.0.0
build(deps): Bump web-auth/webauthn-lib from 4.9.1 to 5.0.0
2024-07-19 13:33:03 +01:00
dependabot[bot]
f384e16ef1
build(deps): Bump web-auth/webauthn-lib from 4.9.1 to 5.0.0
Bumps [web-auth/webauthn-lib](https://github.com/web-auth/webauthn-lib) from 4.9.1 to 5.0.0.
- [Commits](https://github.com/web-auth/webauthn-lib/compare/4.9.1...5.0.0)

---
updated-dependencies:
- dependency-name: web-auth/webauthn-lib
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-19 12:28:52 +00:00
520892f789
Merge pull request #1486 from jonnybarnes/dependabot/composer/guzzlehttp/guzzle-7.9.0
build(deps): Bump guzzlehttp/guzzle from 7.8.1 to 7.9.0
2024-07-19 13:27:59 +01:00
dependabot[bot]
290445a4e1
build(deps): Bump guzzlehttp/guzzle from 7.8.1 to 7.9.0
Bumps [guzzlehttp/guzzle](https://github.com/guzzle/guzzle) from 7.8.1 to 7.9.0.
- [Release notes](https://github.com/guzzle/guzzle/releases)
- [Changelog](https://github.com/guzzle/guzzle/blob/7.9/CHANGELOG.md)
- [Commits](https://github.com/guzzle/guzzle/compare/7.8.1...7.9.0)

---
updated-dependencies:
- dependency-name: guzzlehttp/guzzle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-19 09:54:23 +00:00
e1f6c9b1e3
Merge pull request #1485 from jonnybarnes/dependabot/composer/web-auth/webauthn-lib-4.9.1
build(deps): Bump web-auth/webauthn-lib from 4.9.0 to 4.9.1
2024-07-18 13:12:26 +01:00
dependabot[bot]
8a7f320b3a
build(deps): Bump web-auth/webauthn-lib from 4.9.0 to 4.9.1
Bumps [web-auth/webauthn-lib](https://github.com/web-auth/webauthn-lib) from 4.9.0 to 4.9.1.
- [Commits](https://github.com/web-auth/webauthn-lib/compare/4.9.0...4.9.1)

---
updated-dependencies:
- dependency-name: web-auth/webauthn-lib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-18 09:47:52 +00:00
9ade631b82
Merge pull request #1484 from jonnybarnes/dependabot/composer/laravel/horizon-5.25.0
build(deps): Bump laravel/horizon from 5.24.5 to 5.25.0
2024-07-17 16:04:25 +01:00
dependabot[bot]
43e9fa3530
build(deps): Bump laravel/horizon from 5.24.5 to 5.25.0
Bumps [laravel/horizon](https://github.com/laravel/horizon) from 5.24.5 to 5.25.0.
- [Release notes](https://github.com/laravel/horizon/releases)
- [Changelog](https://github.com/laravel/horizon/blob/5.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/horizon/compare/v5.24.5...v5.25.0)

---
updated-dependencies:
- dependency-name: laravel/horizon
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 14:55:11 +00:00
370f33fcbf
Merge pull request #1483 from jonnybarnes/dependabot/composer/nunomaduro/collision-8.3.0
build(deps-dev): Bump nunomaduro/collision from 8.1.1 to 8.3.0
2024-07-17 15:54:25 +01:00
dependabot[bot]
4250b946f4
build(deps-dev): Bump nunomaduro/collision from 8.1.1 to 8.3.0
Bumps [nunomaduro/collision](https://github.com/nunomaduro/collision) from 8.1.1 to 8.3.0.
- [Changelog](https://github.com/nunomaduro/collision/blob/v8.x/CHANGELOG.md)
- [Commits](https://github.com/nunomaduro/collision/compare/v8.1.1...v8.3.0)

---
updated-dependencies:
- dependency-name: nunomaduro/collision
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-17 09:32:47 +00:00
e2b8b3ba1a
Merge pull request #1481 from jonnybarnes/dependabot/npm_and_yarn/eslint-9.7.0
build(deps-dev): Bump eslint from 9.6.0 to 9.7.0
2024-07-16 21:42:44 +01:00
dependabot[bot]
ed8903050b
build(deps-dev): Bump eslint from 9.6.0 to 9.7.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.6.0 to 9.7.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.6.0...v9.7.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-16 20:41:24 +00:00
6c9ee8bb7d
Merge pull request #1482 from jonnybarnes/dependabot/npm_and_yarn/stylelint-16.7.0
build(deps-dev): Bump stylelint from 16.6.1 to 16.7.0
2024-07-16 21:40:23 +01:00
dependabot[bot]
207ff9fda3
build(deps-dev): Bump stylelint from 16.6.1 to 16.7.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.6.1 to 16.7.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.6.1...16.7.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-16 20:36:57 +00:00
c5e826a3e2
Merge pull request #1479 from jonnybarnes/dependabot/composer/spatie/laravel-ray-1.37.1
build(deps-dev): Bump spatie/laravel-ray from 1.36.2 to 1.37.1
2024-07-16 21:35:33 +01:00
dependabot[bot]
ea2fca89ed
build(deps-dev): Bump spatie/laravel-ray from 1.36.2 to 1.37.1
Bumps [spatie/laravel-ray](https://github.com/spatie/laravel-ray) from 1.36.2 to 1.37.1.
- [Release notes](https://github.com/spatie/laravel-ray/releases)
- [Changelog](https://github.com/spatie/laravel-ray/blob/main/CHANGELOG.md)
- [Commits](https://github.com/spatie/laravel-ray/compare/1.36.2...1.37.1)

---
updated-dependencies:
- dependency-name: spatie/laravel-ray
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-16 20:01:57 +00:00
17a6f15175
Merge pull request #1478 from jonnybarnes/dependabot/composer/barryvdh/laravel-ide-helper-3.1.0
build(deps-dev): Bump barryvdh/laravel-ide-helper from 3.0.0 to 3.1.0
2024-07-16 21:00:41 +01:00
dependabot[bot]
3dd44d23cc
build(deps-dev): Bump barryvdh/laravel-ide-helper from 3.0.0 to 3.1.0
Bumps [barryvdh/laravel-ide-helper](https://github.com/barryvdh/laravel-ide-helper) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/barryvdh/laravel-ide-helper/releases)
- [Changelog](https://github.com/barryvdh/laravel-ide-helper/blob/master/CHANGELOG.md)
- [Commits](https://github.com/barryvdh/laravel-ide-helper/compare/v3.0.0...v3.1.0)

---
updated-dependencies:
- dependency-name: barryvdh/laravel-ide-helper
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-16 19:59:24 +00:00
bfaec2f3a5
Merge pull request #1477 from jonnybarnes/dependabot/composer/intervention/image-3.7.2
build(deps): Bump intervention/image from 3.7.0 to 3.7.2
2024-07-16 20:58:38 +01:00
dependabot[bot]
2d04d68484
build(deps): Bump intervention/image from 3.7.0 to 3.7.2
Bumps [intervention/image](https://github.com/Intervention/image) from 3.7.0 to 3.7.2.
- [Release notes](https://github.com/Intervention/image/releases)
- [Commits](https://github.com/Intervention/image/compare/3.7.0...3.7.2)

---
updated-dependencies:
- dependency-name: intervention/image
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-16 19:51:39 +00:00
fe9ae25a60
Merge pull request #1476 from jonnybarnes/dependabot/composer/web-auth/webauthn-lib-4.9.0
build(deps): Bump web-auth/webauthn-lib from 4.8.7 to 4.9.0
2024-07-16 20:50:41 +01:00
dependabot[bot]
bd09e5a65f
build(deps): Bump web-auth/webauthn-lib from 4.8.7 to 4.9.0
Bumps [web-auth/webauthn-lib](https://github.com/web-auth/webauthn-lib) from 4.8.7 to 4.9.0.
- [Commits](https://github.com/web-auth/webauthn-lib/compare/4.8.7...4.9.0)

---
updated-dependencies:
- dependency-name: web-auth/webauthn-lib
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-16 19:46:47 +00:00
9a883e05bb
Merge pull request #1475 from jonnybarnes/dependabot/composer/laravel/dusk-8.2.1
build(deps-dev): Bump laravel/dusk from 8.2.0 to 8.2.1
2024-07-16 20:46:06 +01:00
dependabot[bot]
01713b7d79
build(deps-dev): Bump laravel/dusk from 8.2.0 to 8.2.1
Bumps [laravel/dusk](https://github.com/laravel/dusk) from 8.2.0 to 8.2.1.
- [Release notes](https://github.com/laravel/dusk/releases)
- [Changelog](https://github.com/laravel/dusk/blob/8.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/dusk/compare/v8.2.0...v8.2.1)

---
updated-dependencies:
- dependency-name: laravel/dusk
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-15 09:09:07 +00:00
d6a0b1dcf6
Merge pull request #1473 from jonnybarnes/1455-scope-verification-is-failing-for-micropub-requests
Improve scope checking
2024-07-13 15:01:54 +01:00
bcf61bb6a0
Fis Laravel Pint issues 2024-07-13 14:58:11 +01:00
baee7ade4f
Improve scope checking
Whether the scopes are defined as a space separated string, or an array,
we should now be checking them without any errors.
2024-07-13 14:52:57 +01:00
55afa8f01d
Merge pull request #1472 from jonnybarnes/update_laravel_pint_20240713
Update Laravel Pint
2024-07-13 10:58:47 +01:00
8e077045d2
Update Laravel Pint
Also fixes some of the filesaftersome new rules were added to the
default pint configuration.
2024-07-13 10:55:19 +01:00
1475ecdde5
Merge pull request #1471 from jonnybarnes/postres-16
Update Postgres in Laravel Sail to v16
2024-07-13 10:38:28 +01:00
a2c0fb2298
Update Postgres in Laravel Sail to v16 2024-07-12 15:15:08 +01:00
7b93692ba5
Merge pull request #1467 from jonnybarnes/dependabot/npm_and_yarn/globals-15.8.0
build(deps-dev): Bump globals from 15.7.0 to 15.8.0
2024-07-12 13:54:33 +01:00
dependabot[bot]
51c3ba8ecf
build(deps-dev): Bump globals from 15.7.0 to 15.8.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.7.0 to 15.8.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.7.0...v15.8.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 12:53:11 +00:00
19f2803590
Merge pull request #1468 from jonnybarnes/dependabot/composer/laravel/sail-1.30.2
build(deps-dev): Bump laravel/sail from 1.29.3 to 1.30.2
2024-07-12 13:48:28 +01:00
dependabot[bot]
be95bd6468
build(deps-dev): Bump laravel/sail from 1.29.3 to 1.30.2
Bumps [laravel/sail](https://github.com/laravel/sail) from 1.29.3 to 1.30.2.
- [Release notes](https://github.com/laravel/sail/releases)
- [Changelog](https://github.com/laravel/sail/blob/1.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/sail/compare/v1.29.3...v1.30.2)

---
updated-dependencies:
- dependency-name: laravel/sail
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 12:46:53 +00:00
96f340ddec
Merge pull request #1464 from jonnybarnes/dependabot/composer/laravel/scout-10.10.1
build(deps): Bump laravel/scout from 10.9.0 to 10.10.1
2024-07-12 13:46:07 +01:00
dependabot[bot]
8792bee249
build(deps): Bump laravel/scout from 10.9.0 to 10.10.1
Bumps [laravel/scout](https://github.com/laravel/scout) from 10.9.0 to 10.10.1.
- [Release notes](https://github.com/laravel/scout/releases)
- [Changelog](https://github.com/laravel/scout/blob/10.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/scout/compare/v10.9.0...v10.10.1)

---
updated-dependencies:
- dependency-name: laravel/scout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 12:44:49 +00:00
d628edf213
Merge pull request #1470 from jonnybarnes/dependabot/composer/laravel/framework-11.15.0
build(deps): Bump laravel/framework from 11.13.0 to 11.15.0
2024-07-12 13:41:44 +01:00
dependabot[bot]
05769c410c
build(deps): Bump laravel/framework from 11.13.0 to 11.15.0
Bumps [laravel/framework](https://github.com/laravel/framework) from 11.13.0 to 11.15.0.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/11.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v11.13.0...v11.15.0)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 12:39:54 +00:00
024cc73c9d
Merge pull request #1463 from jonnybarnes/dependabot/composer/symfony/serializer-7.1.2
build(deps): Bump symfony/serializer from 7.1.1 to 7.1.2
2024-07-12 13:38:49 +01:00
dependabot[bot]
c25c1672f1
build(deps): Bump symfony/serializer from 7.1.1 to 7.1.2
Bumps [symfony/serializer](https://github.com/symfony/serializer) from 7.1.1 to 7.1.2.
- [Release notes](https://github.com/symfony/serializer/releases)
- [Changelog](https://github.com/symfony/serializer/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/serializer/compare/v7.1.1...v7.1.2)

---
updated-dependencies:
- dependency-name: symfony/serializer
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 19:15:17 +00:00
47b8208bea
Merge pull request #1459 from jonnybarnes/dependabot/composer/vimeo/psalm-5.25.0
build(deps-dev): Bump vimeo/psalm from 5.24.0 to 5.25.0
2024-07-02 20:14:32 +01:00
dependabot[bot]
ed8d232ce4
build(deps-dev): Bump vimeo/psalm from 5.24.0 to 5.25.0
Bumps [vimeo/psalm](https://github.com/vimeo/psalm) from 5.24.0 to 5.25.0.
- [Release notes](https://github.com/vimeo/psalm/releases)
- [Commits](https://github.com/vimeo/psalm/compare/5.24.0...5.25.0)

---
updated-dependencies:
- dependency-name: vimeo/psalm
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 19:09:29 +00:00
1fa4d71714
Merge pull request #1458 from jonnybarnes/dependabot/npm_and_yarn/eslint-9.6.0
build(deps-dev): Bump eslint from 9.5.0 to 9.6.0
2024-07-02 20:08:29 +01:00
dependabot[bot]
8baac038de
build(deps-dev): Bump eslint from 9.5.0 to 9.6.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.5.0 to 9.6.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.5.0...v9.6.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 19:06:40 +00:00
49078156f1
Merge pull request #1457 from jonnybarnes/dependabot/npm_and_yarn/eslint/js-9.6.0
build(deps-dev): Bump @eslint/js from 9.5.0 to 9.6.0
2024-07-02 20:05:28 +01:00
dependabot[bot]
929f26fe76
build(deps-dev): Bump @eslint/js from 9.5.0 to 9.6.0
Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.5.0 to 9.6.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/commits/v9.6.0/packages/js)

---
updated-dependencies:
- dependency-name: "@eslint/js"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-02 19:02:28 +00:00
e0f82643e0
Merge pull request #1456 from jonnybarnes/dependabot/npm_and_yarn/globals-15.7.0
build(deps-dev): Bump globals from 15.6.0 to 15.7.0
2024-07-02 20:01:43 +01:00
dependabot[bot]
f8063d1bca
build(deps-dev): Bump globals from 15.6.0 to 15.7.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.6.0 to 15.7.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.6.0...v15.7.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 09:09:47 +00:00
afee38c04b
Merge pull request #1453 from jonnybarnes/1452-tokens-can-not-be-issued
Store scope data correctly
2024-06-30 11:18:37 +01:00
3cf11b0d72
Store scope data correctly
I was using the key `scopes` instead of `scope`
2024-06-30 11:13:27 +01:00
06c5d811be
Merge pull request #1451 from jonnybarnes/dependabot/npm_and_yarn/stylistic/eslint-plugin-2.3.0
build(deps-dev): Bump @stylistic/eslint-plugin from 2.2.2 to 2.3.0
2024-06-30 09:49:54 +01:00
dependabot[bot]
411dc19c5a
build(deps-dev): Bump @stylistic/eslint-plugin from 2.2.2 to 2.3.0
Bumps [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) from 2.2.2 to 2.3.0.
- [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases)
- [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v2.3.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@stylistic/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-29 14:43:05 +00:00
179938d97c
Merge pull request #1450 from jonnybarnes/dependabot/composer/spatie/laravel-ignition-2.8.0
build(deps): Bump spatie/laravel-ignition from 2.7.0 to 2.8.0
2024-06-29 15:42:22 +01:00
dependabot[bot]
0e68fffc16
build(deps): Bump spatie/laravel-ignition from 2.7.0 to 2.8.0
Bumps [spatie/laravel-ignition](https://github.com/spatie/laravel-ignition) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/spatie/laravel-ignition/releases)
- [Changelog](https://github.com/spatie/laravel-ignition/blob/main/CHANGELOG.md)
- [Commits](https://github.com/spatie/laravel-ignition/compare/2.7.0...2.8.0)

---
updated-dependencies:
- dependency-name: spatie/laravel-ignition
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-29 14:40:54 +00:00
6a575f9058
Merge pull request #1449 from jonnybarnes/dependabot/composer/openai-php/client-0.10.1
build(deps-dev): Bump openai-php/client from 0.8.4 to 0.10.1
2024-06-29 15:40:11 +01:00
dependabot[bot]
0eea92930b
build(deps-dev): Bump openai-php/client from 0.8.4 to 0.10.1
Bumps [openai-php/client](https://github.com/openai-php/client) from 0.8.4 to 0.10.1.
- [Release notes](https://github.com/openai-php/client/releases)
- [Changelog](https://github.com/openai-php/client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/openai-php/client/compare/v0.8.4...v0.10.1)

---
updated-dependencies:
- dependency-name: openai-php/client
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-29 14:38:22 +00:00
e8a9edc2bb
Merge pull request #1448 from jonnybarnes/dependabot/composer/lcobucci/jwt-5.3.0
build(deps): Bump lcobucci/jwt from 5.2.0 to 5.3.0
2024-06-29 15:37:44 +01:00
dependabot[bot]
74db2cf3e1
build(deps): Bump lcobucci/jwt from 5.2.0 to 5.3.0
Bumps [lcobucci/jwt](https://github.com/lcobucci/jwt) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/lcobucci/jwt/releases)
- [Commits](https://github.com/lcobucci/jwt/compare/5.2.0...5.3.0)

---
updated-dependencies:
- dependency-name: lcobucci/jwt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-29 14:33:25 +00:00
0fee4eedfa
Merge pull request #1447 from jonnybarnes/dependabot/composer/symfony/property-access-7.1.1
build(deps): Bump symfony/property-access from 7.0.4 to 7.1.1
2024-06-29 15:32:47 +01:00
dependabot[bot]
5481bab429
build(deps): Bump symfony/property-access from 7.0.4 to 7.1.1
Bumps [symfony/property-access](https://github.com/symfony/property-access) from 7.0.4 to 7.1.1.
- [Release notes](https://github.com/symfony/property-access/releases)
- [Changelog](https://github.com/symfony/property-access/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/property-access/compare/v7.0.4...v7.1.1)

---
updated-dependencies:
- dependency-name: symfony/property-access
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-29 14:25:02 +00:00
55a1444ba8
Merge pull request #1446 from jonnybarnes/dependabot/composer/symfony/serializer-7.1.1
build(deps): Bump symfony/serializer from 7.0.4 to 7.1.1
2024-06-29 15:24:26 +01:00
dependabot[bot]
145c388b65
build(deps): Bump symfony/serializer from 7.0.4 to 7.1.1
Bumps [symfony/serializer](https://github.com/symfony/serializer) from 7.0.4 to 7.1.1.
- [Release notes](https://github.com/symfony/serializer/releases)
- [Changelog](https://github.com/symfony/serializer/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/serializer/compare/v7.0.4...v7.1.1)

---
updated-dependencies:
- dependency-name: symfony/serializer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-24 09:58:14 +00:00
cabc9fef84
Merge pull request #1444 from jonnybarnes/1443-scopes-arent-being-shown-correctly-in-indieauth-approve-page
Parse scope value from authorization URL correctly
2024-06-22 20:51:17 +01:00
1521130f55
Parse scope value from authorization URL correctly 2024-06-22 20:48:03 +01:00
de3661ab77
Merge pull request #1441 from jonnybarnes/1440-make-login-work-with-indieauth-flow
Save full url during login
2024-06-22 20:30:51 +01:00
5d8929ac29
Save full url during login
This means the query params should be kept when redirecting back to
`/auth?...` during the IndieAuth flow.
2024-06-22 20:28:18 +01:00
1cd4e54b44
Merge pull request #1438 from jonnybarnes/1437-fix-auth-endpoint-in-html-head-links
Improve html links in the head regarding IndieAuth
2024-06-22 18:44:09 +01:00
3e980b9a6a
Improve html links in the head regarding IndieAuth
One of them had a typo in
2024-06-22 18:40:41 +01:00
427a63af0f
Merge pull request #1435 from jonnybarnes/dependabot/npm_and_yarn/stylelint-config-standard-36.0.1
build(deps-dev): Bump stylelint-config-standard from 36.0.0 to 36.0.1
2024-06-22 18:20:02 +01:00
dependabot[bot]
466378433b
build(deps-dev): Bump stylelint-config-standard from 36.0.0 to 36.0.1
Bumps [stylelint-config-standard](https://github.com/stylelint/stylelint-config-standard) from 36.0.0 to 36.0.1.
- [Release notes](https://github.com/stylelint/stylelint-config-standard/releases)
- [Changelog](https://github.com/stylelint/stylelint-config-standard/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint-config-standard/compare/36.0.0...36.0.1)

---
updated-dependencies:
- dependency-name: stylelint-config-standard
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-22 17:17:41 +00:00
bf94de46ab
Merge pull request #1434 from jonnybarnes/dependabot/npm_and_yarn/stylistic/eslint-plugin-2.2.2
build(deps-dev): Bump @stylistic/eslint-plugin from 2.1.0 to 2.2.2
2024-06-22 18:17:09 +01:00
dependabot[bot]
92d66f733e
build(deps-dev): Bump @stylistic/eslint-plugin from 2.1.0 to 2.2.2
Bumps [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) from 2.1.0 to 2.2.2.
- [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases)
- [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v2.2.2/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@stylistic/eslint-plugin"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-22 17:15:37 +00:00
cb32b30663
Merge pull request #1431 from jonnybarnes/dependabot/npm_and_yarn/globals-15.6.0
build(deps-dev): Bump globals from 15.4.0 to 15.6.0
2024-06-22 18:15:01 +01:00
dependabot[bot]
a49ee852ee
build(deps-dev): Bump globals from 15.4.0 to 15.6.0
Bumps [globals](https://github.com/sindresorhus/globals) from 15.4.0 to 15.6.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v15.4.0...v15.6.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-22 17:11:08 +00:00
84940844b0
Merge pull request #1433 from jonnybarnes/dependabot/composer/laravel/sail-1.29.3
build(deps-dev): Bump laravel/sail from 1.29.1 to 1.29.3
2024-06-22 18:10:22 +01:00
dependabot[bot]
0991c53dd6
build(deps-dev): Bump laravel/sail from 1.29.1 to 1.29.3
Bumps [laravel/sail](https://github.com/laravel/sail) from 1.29.1 to 1.29.3.
- [Release notes](https://github.com/laravel/sail/releases)
- [Changelog](https://github.com/laravel/sail/blob/1.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/sail/compare/v1.29.1...v1.29.3)

---
updated-dependencies:
- dependency-name: laravel/sail
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-22 17:08:19 +00:00
f7c9c044e0
Merge pull request #1430 from jonnybarnes/dependabot/composer/laravel/sanctum-4.0.2
build(deps): Bump laravel/sanctum from 4.0.1 to 4.0.2
2024-06-22 18:07:34 +01:00
dependabot[bot]
9348b7b0ce
build(deps): Bump laravel/sanctum from 4.0.1 to 4.0.2
Bumps [laravel/sanctum](https://github.com/laravel/sanctum) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/laravel/sanctum/releases)
- [Changelog](https://github.com/laravel/sanctum/blob/4.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/sanctum/compare/v4.0.1...v4.0.2)

---
updated-dependencies:
- dependency-name: laravel/sanctum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-22 17:05:16 +00:00
527bfb31d6
Merge pull request #1428 from jonnybarnes/dependabot/composer/web-auth/webauthn-lib-4.8.7
build(deps): Bump web-auth/webauthn-lib from 4.8.3 to 4.8.7
2024-06-22 18:04:33 +01:00
dependabot[bot]
f7f47a22f0
build(deps): Bump web-auth/webauthn-lib from 4.8.3 to 4.8.7
Bumps [web-auth/webauthn-lib](https://github.com/web-auth/webauthn-lib) from 4.8.3 to 4.8.7.
- [Commits](https://github.com/web-auth/webauthn-lib/compare/4.8.3...4.8.7)

---
updated-dependencies:
- dependency-name: web-auth/webauthn-lib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-22 16:57:27 +00:00
0066311bde
Merge pull request #1427 from jonnybarnes/dependabot/composer/intervention/image-3.7.0
build(deps): Bump intervention/image from 3.5.1 to 3.7.0
2024-06-22 17:56:49 +01:00
dependabot[bot]
a015ba2775
build(deps): Bump intervention/image from 3.5.1 to 3.7.0
Bumps [intervention/image](https://github.com/Intervention/image) from 3.5.1 to 3.7.0.
- [Release notes](https://github.com/Intervention/image/releases)
- [Commits](https://github.com/Intervention/image/compare/3.5.1...3.7.0)

---
updated-dependencies:
- dependency-name: intervention/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-22 16:50:58 +00:00
f456199c48
Merge pull request #1426 from jonnybarnes/dependabot/composer/league/flysystem-aws-s3-v3-3.28.0
build(deps): Bump league/flysystem-aws-s3-v3 from 3.26.0 to 3.28.0
2024-06-22 17:50:17 +01:00
dependabot[bot]
78712aef2f
build(deps): Bump league/flysystem-aws-s3-v3 from 3.26.0 to 3.28.0
Bumps [league/flysystem-aws-s3-v3](https://github.com/thephpleague/flysystem-aws-s3-v3) from 3.26.0 to 3.28.0.
- [Commits](https://github.com/thephpleague/flysystem-aws-s3-v3/compare/3.26.0...3.28.0)

---
updated-dependencies:
- dependency-name: league/flysystem-aws-s3-v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-22 16:48:33 +00:00
bb980eec47
Merge pull request #1422 from jonnybarnes/dependabot/npm_and_yarn/eslint-9.5.0
build(deps-dev): Bump eslint from 9.4.0 to 9.5.0
2024-06-22 17:46:38 +01:00
dependabot[bot]
4749b166bf
build(deps-dev): Bump eslint from 9.4.0 to 9.5.0
Bumps [eslint](https://github.com/eslint/eslint) from 9.4.0 to 9.5.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.4.0...v9.5.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 09:05:37 +00:00
f51e2610f4
Merge pull request #1421 from jonnybarnes/dependabot/composer/laravel/scout-10.9.0
build(deps): Bump laravel/scout from 10.8.4 to 10.9.0
2024-06-16 16:05:35 +01:00
dependabot[bot]
ae0a8290f7
build(deps): Bump laravel/scout from 10.8.4 to 10.9.0
Bumps [laravel/scout](https://github.com/laravel/scout) from 10.8.4 to 10.9.0.
- [Release notes](https://github.com/laravel/scout/releases)
- [Changelog](https://github.com/laravel/scout/blob/10.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/scout/compare/v10.8.4...v10.9.0)

---
updated-dependencies:
- dependency-name: laravel/scout
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-16 15:02:32 +00:00
6afa01b58d
Merge pull request #1420 from jonnybarnes/dependabot/composer/symfony/html-sanitizer-7.1.1
build(deps): Bump symfony/html-sanitizer from 7.0.4 to 7.1.1
2024-06-16 16:01:47 +01:00
dependabot[bot]
0ae30c18f0
build(deps): Bump symfony/html-sanitizer from 7.0.4 to 7.1.1
Bumps [symfony/html-sanitizer](https://github.com/symfony/html-sanitizer) from 7.0.4 to 7.1.1.
- [Release notes](https://github.com/symfony/html-sanitizer/releases)
- [Changelog](https://github.com/symfony/html-sanitizer/blob/7.1/CHANGELOG.md)
- [Commits](https://github.com/symfony/html-sanitizer/compare/v7.0.4...v7.1.1)

---
updated-dependencies:
- dependency-name: symfony/html-sanitizer
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-16 14:52:27 +00:00
0b864a3286
Merge pull request #1419 from jonnybarnes/dependabot/composer/laravel/horizon-5.24.5
build(deps): Bump laravel/horizon from 5.23.2 to 5.24.5
2024-06-16 15:51:47 +01:00
dependabot[bot]
5a6db7dee4
build(deps): Bump laravel/horizon from 5.23.2 to 5.24.5
Bumps [laravel/horizon](https://github.com/laravel/horizon) from 5.23.2 to 5.24.5.
- [Release notes](https://github.com/laravel/horizon/releases)
- [Changelog](https://github.com/laravel/horizon/blob/5.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/horizon/compare/v5.23.2...v5.24.5)

---
updated-dependencies:
- dependency-name: laravel/horizon
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-16 14:43:18 +00:00
0e76d42743
Merge pull request #1418 from jonnybarnes/dependabot/composer/mockery/mockery-1.6.12
build(deps-dev): Bump mockery/mockery from 1.6.11 to 1.6.12
2024-06-16 15:42:32 +01:00
dependabot[bot]
39770f6f7c
build(deps-dev): Bump mockery/mockery from 1.6.11 to 1.6.12
Bumps [mockery/mockery](https://github.com/mockery/mockery) from 1.6.11 to 1.6.12.
- [Release notes](https://github.com/mockery/mockery/releases)
- [Changelog](https://github.com/mockery/mockery/blob/1.6.x/CHANGELOG.md)
- [Commits](https://github.com/mockery/mockery/compare/1.6.11...1.6.12)

---
updated-dependencies:
- dependency-name: mockery/mockery
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-16 14:39:19 +00:00
df0a388da6
Merge pull request #1417 from jonnybarnes/dependabot/composer/phpunit/phpunit-10.5.20
build(deps-dev): Bump phpunit/phpunit from 10.5.16 to 10.5.20
2024-06-16 15:37:57 +01:00
dependabot[bot]
6943197ec5
build(deps-dev): Bump phpunit/phpunit from 10.5.16 to 10.5.20
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 10.5.16 to 10.5.20.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.5.20/ChangeLog-10.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/10.5.16...10.5.20)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-10 09:06:54 +00:00
9855c5ce04
Merge pull request #1416 from jonnybarnes/1415-fix-eslint-configuration
Update eslint config
2024-06-08 21:00:36 +01:00
8432934643
Update eslint config 2024-06-08 20:55:34 +01:00
5a52fda97d
Merge pull request #1413 from jonnybarnes/dependabot/npm_and_yarn/eslint-9.4.0
build(deps-dev): Bump eslint from 8.57.0 to 9.4.0
2024-06-08 20:16:06 +01:00
dependabot[bot]
ba13e3e4ca
build(deps-dev): Bump eslint from 8.57.0 to 9.4.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.57.0 to 9.4.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.57.0...v9.4.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-08 19:14:18 +00:00
ca6eefd0a9
Merge pull request #1412 from jonnybarnes/dependabot/npm_and_yarn/stylelint-16.6.1
build(deps-dev): Bump stylelint from 16.3.1 to 16.6.1
2024-06-08 20:13:39 +01:00
dependabot[bot]
9496648221
build(deps-dev): Bump stylelint from 16.3.1 to 16.6.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.3.1 to 16.6.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.3.1...16.6.1)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-08 19:06:07 +00:00
c40bbf3a53
Merge pull request #1407 from jonnybarnes/dependabot/composer/spatie/laravel-ignition-2.7.0
build(deps): Bump spatie/laravel-ignition from 2.4.2 to 2.7.0
2024-06-08 20:05:21 +01:00
dependabot[bot]
548f156ad0
build(deps): Bump spatie/laravel-ignition from 2.4.2 to 2.7.0
Bumps [spatie/laravel-ignition](https://github.com/spatie/laravel-ignition) from 2.4.2 to 2.7.0.
- [Release notes](https://github.com/spatie/laravel-ignition/releases)
- [Changelog](https://github.com/spatie/laravel-ignition/blob/main/CHANGELOG.md)
- [Commits](https://github.com/spatie/laravel-ignition/compare/2.4.2...2.7.0)

---
updated-dependencies:
- dependency-name: spatie/laravel-ignition
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-08 18:59:45 +00:00
42f69bd168
Merge pull request #1406 from jonnybarnes/dependabot/composer/spatie/laravel-ray-1.36.2
build(deps-dev): Bump spatie/laravel-ray from 1.35.1 to 1.36.2
2024-06-08 19:58:58 +01:00
dependabot[bot]
333412c810
build(deps-dev): Bump spatie/laravel-ray from 1.35.1 to 1.36.2
Bumps [spatie/laravel-ray](https://github.com/spatie/laravel-ray) from 1.35.1 to 1.36.2.
- [Release notes](https://github.com/spatie/laravel-ray/releases)
- [Changelog](https://github.com/spatie/laravel-ray/blob/main/CHANGELOG.md)
- [Commits](https://github.com/spatie/laravel-ray/compare/1.35.1...1.36.2)

---
updated-dependencies:
- dependency-name: spatie/laravel-ray
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-08 18:56:04 +00:00
699096c008
Merge pull request #1404 from jonnybarnes/dependabot/composer/barryvdh/laravel-debugbar-3.13.5
build(deps-dev): Bump barryvdh/laravel-debugbar from 3.12.2 to 3.13.5
2024-06-08 19:55:16 +01:00
dependabot[bot]
6de8f25a31
build(deps-dev): Bump barryvdh/laravel-debugbar from 3.12.2 to 3.13.5
Bumps [barryvdh/laravel-debugbar](https://github.com/barryvdh/laravel-debugbar) from 3.12.2 to 3.13.5.
- [Release notes](https://github.com/barryvdh/laravel-debugbar/releases)
- [Commits](https://github.com/barryvdh/laravel-debugbar/compare/v3.12.2...v3.13.5)

---
updated-dependencies:
- dependency-name: barryvdh/laravel-debugbar
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-08 18:53:16 +00:00
255ff5227f
Merge pull request #1397 from jonnybarnes/dependabot/composer/laravel/dusk-8.2.0
build(deps-dev): Bump laravel/dusk from 8.1.0 to 8.2.0
2024-06-08 19:52:30 +01:00
dependabot[bot]
e84d1018cc
build(deps-dev): Bump laravel/dusk from 8.1.0 to 8.2.0
Bumps [laravel/dusk](https://github.com/laravel/dusk) from 8.1.0 to 8.2.0.
- [Release notes](https://github.com/laravel/dusk/releases)
- [Changelog](https://github.com/laravel/dusk/blob/8.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/dusk/compare/v8.1.0...v8.2.0)

---
updated-dependencies:
- dependency-name: laravel/dusk
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-08 18:50:00 +00:00
72b0a4f133
Merge pull request #1388 from jonnybarnes/dependabot/composer/psalm/plugin-laravel-2.11.0
build(deps-dev): Bump psalm/plugin-laravel from 2.10.1 to 2.11.0
2024-06-08 19:49:10 +01:00
dependabot[bot]
4a8e2dd0fc
build(deps-dev): Bump psalm/plugin-laravel from 2.10.1 to 2.11.0
Bumps [psalm/plugin-laravel](https://github.com/psalm/psalm-plugin-laravel) from 2.10.1 to 2.11.0.
- [Release notes](https://github.com/psalm/psalm-plugin-laravel/releases)
- [Commits](https://github.com/psalm/psalm-plugin-laravel/compare/v2.10.1...v2.11.0)

---
updated-dependencies:
- dependency-name: psalm/plugin-laravel
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-08 18:46:56 +00:00
a440533a76
Merge pull request #1414 from jonnybarnes/1384-implement-auth-endpoint
Implement IndieAuth endpoint
2024-06-08 19:45:36 +01:00
d98a66f42b
Laravel Pint fixes 2024-06-08 19:43:44 +01:00
58b31bb4c1
Add Indieweb related link to the HTTP headers 2024-06-08 19:39:09 +01:00
7f70f75d05
IndieAuth endpoint can now return access tokens 2024-06-08 10:56:15 +01:00
5b2bfd5270
Auth endpoint
The IndieAuth endpoint should be added, currently adding the unt tests
2024-06-02 10:16:16 +01:00
7ad5d56f1b
Merge pull request #1383 from jonnybarnes/dependabot/composer/laravel/framework-11.1.1
build(deps): Bump laravel/framework from 11.1.0 to 11.1.1
2024-03-29 14:29:07 +00:00
dependabot[bot]
7f95842308
build(deps): Bump laravel/framework from 11.1.0 to 11.1.1
Bumps [laravel/framework](https://github.com/laravel/framework) from 11.1.0 to 11.1.1.
- [Release notes](https://github.com/laravel/framework/releases)
- [Changelog](https://github.com/laravel/framework/blob/11.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/framework/compare/v11.1.0...v11.1.1)

---
updated-dependencies:
- dependency-name: laravel/framework
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 14:25:05 +00:00
25fceca2ff
Merge pull request #1382 from jonnybarnes/dependabot/composer/phpunit/phpunit-10.5.16
build(deps-dev): Bump phpunit/phpunit from 10.5.15 to 10.5.16
2024-03-29 14:24:01 +00:00
dependabot[bot]
dd862be3a9
build(deps-dev): Bump phpunit/phpunit from 10.5.15 to 10.5.16
Bumps [phpunit/phpunit](https://github.com/sebastianbergmann/phpunit) from 10.5.15 to 10.5.16.
- [Release notes](https://github.com/sebastianbergmann/phpunit/releases)
- [Changelog](https://github.com/sebastianbergmann/phpunit/blob/10.5.16/ChangeLog-10.5.md)
- [Commits](https://github.com/sebastianbergmann/phpunit/compare/10.5.15...10.5.16)

---
updated-dependencies:
- dependency-name: phpunit/phpunit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 14:18:35 +00:00
1b08703cd9
Merge pull request #1381 from jonnybarnes/dependabot/composer/laravel/pint-1.15.0
build(deps-dev): Bump laravel/pint from 1.14.0 to 1.15.0
2024-03-29 14:17:44 +00:00
dependabot[bot]
1475e8f859
build(deps-dev): Bump laravel/pint from 1.14.0 to 1.15.0
Bumps [laravel/pint](https://github.com/laravel/pint) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/laravel/pint/releases)
- [Changelog](https://github.com/laravel/pint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/laravel/pint/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: laravel/pint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-28 09:53:40 +00:00
e72d9ca231
Merge pull request #1380 from jonnybarnes/dependabot/composer/laravel/dusk-8.1.0
build(deps-dev): Bump laravel/dusk from 8.0.0 to 8.1.0
2024-03-27 16:20:29 +00:00
dependabot[bot]
1db7cde639
build(deps-dev): Bump laravel/dusk from 8.0.0 to 8.1.0
Bumps [laravel/dusk](https://github.com/laravel/dusk) from 8.0.0 to 8.1.0.
- [Release notes](https://github.com/laravel/dusk/releases)
- [Changelog](https://github.com/laravel/dusk/blob/8.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/dusk/compare/v8.0.0...v8.1.0)

---
updated-dependencies:
- dependency-name: laravel/dusk
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 16:13:42 +00:00
76061e10e3
Merge pull request #1379 from jonnybarnes/dependabot/composer/laravel/horizon-5.23.2
build(deps): Bump laravel/horizon from 5.23.1 to 5.23.2
2024-03-27 16:12:45 +00:00
dependabot[bot]
936acc6196
build(deps): Bump laravel/horizon from 5.23.1 to 5.23.2
Bumps [laravel/horizon](https://github.com/laravel/horizon) from 5.23.1 to 5.23.2.
- [Release notes](https://github.com/laravel/horizon/releases)
- [Changelog](https://github.com/laravel/horizon/blob/5.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/horizon/compare/v5.23.1...v5.23.2)

---
updated-dependencies:
- dependency-name: laravel/horizon
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 16:11:04 +00:00
13e2f8ade1
Merge pull request #1378 from jonnybarnes/dependabot/composer/laravel/scout-10.8.4
build(deps): Bump laravel/scout from 10.8.3 to 10.8.4
2024-03-27 16:09:49 +00:00
dependabot[bot]
7d47ba6edf
build(deps): Bump laravel/scout from 10.8.3 to 10.8.4
Bumps [laravel/scout](https://github.com/laravel/scout) from 10.8.3 to 10.8.4.
- [Release notes](https://github.com/laravel/scout/releases)
- [Changelog](https://github.com/laravel/scout/blob/10.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/scout/compare/v10.8.3...v10.8.4)

---
updated-dependencies:
- dependency-name: laravel/scout
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 15:05:21 +00:00
71f35e6c2e
Merge pull request #1377 from jonnybarnes/dependabot/composer/laravel/sanctum-4.0.1
build(deps): Bump laravel/sanctum from 4.0.0 to 4.0.1
2024-03-27 15:03:23 +00:00
dependabot[bot]
8d053195d1
build(deps): Bump laravel/sanctum from 4.0.0 to 4.0.1
Bumps [laravel/sanctum](https://github.com/laravel/sanctum) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/laravel/sanctum/releases)
- [Changelog](https://github.com/laravel/sanctum/blob/4.x/CHANGELOG.md)
- [Commits](https://github.com/laravel/sanctum/compare/v4.0.0...v4.0.1)

---
updated-dependencies:
- dependency-name: laravel/sanctum
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 14:57:30 +00:00
5c420584c0
Merge pull request #1376 from jonnybarnes/dependabot/npm_and_yarn/stylelint-16.3.1
build(deps-dev): Bump stylelint from 16.3.0 to 16.3.1
2024-03-27 14:27:11 +00:00
dependabot[bot]
4840120971
build(deps-dev): Bump stylelint from 16.3.0 to 16.3.1
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.3.0 to 16.3.1.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.3.0...16.3.1)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-27 14:25:05 +00:00
c26e528d9f
Merge pull request #1375 from jonnybarnes/dependabot/composer/league/flysystem-aws-s3-v3-3.26.0
build(deps): Bump league/flysystem-aws-s3-v3 from 3.25.1 to 3.26.0
2024-03-27 14:21:42 +00:00
dependabot[bot]
9a2364563a
build(deps): Bump league/flysystem-aws-s3-v3 from 3.25.1 to 3.26.0
Bumps [league/flysystem-aws-s3-v3](https://github.com/thephpleague/flysystem-aws-s3-v3) from 3.25.1 to 3.26.0.
- [Commits](https://github.com/thephpleague/flysystem-aws-s3-v3/compare/3.25.1...3.26.0)

---
updated-dependencies:
- dependency-name: league/flysystem-aws-s3-v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-26 09:15:47 +00:00
5749bf2e69
Merge pull request #1374 from jonnybarnes/dependabot/npm_and_yarn/stylelint-16.3.0 2024-03-25 20:02:08 +00:00
dependabot[bot]
32ad6d7d88
build(deps-dev): Bump stylelint from 16.2.1 to 16.3.0
Bumps [stylelint](https://github.com/stylelint/stylelint) from 16.2.1 to 16.3.0.
- [Release notes](https://github.com/stylelint/stylelint/releases)
- [Changelog](https://github.com/stylelint/stylelint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/stylelint/stylelint/compare/16.2.1...16.3.0)

---
updated-dependencies:
- dependency-name: stylelint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-25 09:50:40 +00:00
a200c9d43e
Merge pull request #1370 from jonnybarnes/1284-setup-bluesky-syndication-with-bridgy
Setup support for syndicating to Bluesky
2024-03-23 21:26:35 +00:00
1f9a8fee99
Laravel Pint fixes 2024-03-23 21:24:33 +00:00
cbbe87e23c
Setup support for syndicating to Bluesky 2024-03-23 21:19:54 +00:00
5d6d611707
Merge pull request #1369 from jonnybarnes/dependabot/composer/intervention/image-3.5.1
build(deps): Bump intervention/image from 3.5.0 to 3.5.1
2024-03-23 20:25:15 +00:00
dependabot[bot]
16ce12b5df
build(deps): Bump intervention/image from 3.5.0 to 3.5.1
Bumps [intervention/image](https://github.com/Intervention/image) from 3.5.0 to 3.5.1.
- [Release notes](https://github.com/Intervention/image/releases)
- [Commits](https://github.com/Intervention/image/compare/3.5.0...3.5.1)

---
updated-dependencies:
- dependency-name: intervention/image
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-23 20:18:51 +00:00
d172a1ace8
Merge pull request #1368 from jonnybarnes/dependabot/composer/web-auth/webauthn-lib-4.8.3
build(deps): Bump web-auth/webauthn-lib from 4.8.2 to 4.8.3
2024-03-23 20:17:57 +00:00
dependabot[bot]
b0ce4efbd8
build(deps): Bump web-auth/webauthn-lib from 4.8.2 to 4.8.3
Bumps [web-auth/webauthn-lib](https://github.com/web-auth/webauthn-lib) from 4.8.2 to 4.8.3.
- [Commits](https://github.com/web-auth/webauthn-lib/compare/4.8.2...4.8.3)

---
updated-dependencies:
- dependency-name: web-auth/webauthn-lib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-23 20:10:37 +00:00
8143f7a1a6
Merge pull request #1367 from jonnybarnes/dependabot/github_actions/actions/cache-4
build(deps): Bump actions/cache from 3 to 4
2024-03-23 19:55:36 +00:00
dependabot[bot]
db670de2f0
build(deps): Bump actions/cache from 3 to 4
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-23 19:53:08 +00:00
1429563b74
Merge pull request #1366 from jonnybarnes/dependabot/github_actions/actions/checkout-4
build(deps): Bump actions/checkout from 3 to 4
2024-03-23 19:52:35 +00:00
dependabot[bot]
38480ddaad
build(deps): Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-23 19:49:36 +00:00
3bb33cdec7
Merge pull request #1365 from jonnybarnes/1364-update-github-action-dependencies-with-dependabot
Update GitHub Action dependencies with dependabot
2024-03-23 19:49:16 +00:00
7f8553127d
Update GitHub Action dependencies with dependabot 2024-03-23 19:47:00 +00:00
8726fc329c
Merge pull request #1362 from jonnybarnes/1358-move-csp-out-of-app-code
Remove CSP header
2024-03-23 14:48:16 +00:00
ed2b3c99da
Laravel Pint fixes 2024-03-23 14:42:21 +00:00
db8f885092
Remove CSP header 2024-03-23 14:37:30 +00:00
174cd4db33
Merge pull request #1360 from jonnybarnes/1359-micropub-token-endpoint-broken-after-laravel-11-upgrade
Exclude certain routes from csrf protection
2024-03-23 14:20:06 +00:00
e95186e1fd
Exclude certain routes from csrf protection 2024-03-23 14:18:02 +00:00
03b8c1677c
Merge pull request #1356 from jonnybarnes/1355-update-csp
Update CSP
2024-03-22 19:15:35 +00:00
cf62d756af
Update CSP
Mastodon webmention images are served via my instance, so add to the CSP
2024-03-22 19:05:45 +00:00
dfe74e7538
Merge pull request #1353 from jonnybarnes/1349-webmentions-are-not-showing
Fix processing of profile image for bridgy webmentions
2024-03-22 18:57:53 +00:00
47b39edaeb
Fix processing of profile image for bridgy webmentions 2024-03-22 18:50:25 +00:00
21e2b5217e
Merge pull request #1351 from jonnybarnes/1350-l11-flare-integration
Add flare config back
2024-03-22 18:28:48 +00:00
d877061077
Add flare config back 2024-03-22 18:26:37 +00:00
1cb1eac8fe
Merge pull request #1347 from jonnybarnes/1321-fix-webmentions
Webmentions were being saved with wrong model relationship
2024-03-22 17:48:53 +00:00
12d3da5fdb
Webmentions were being saved with wrong model relationship
Fix the process webmention job to use the correct model relationship,
add an artisan command to update existing webmentions in the database to
use the correct model relationship.
2024-03-22 17:43:23 +00:00
251 changed files with 8035 additions and 7427 deletions

View file

@ -1,14 +0,0 @@
APP_ENV=testing
APP_DEBUG=true
APP_KEY=base64:6DJhvZLVjE6dD4Cqrteh+6Z5vZlG+v/soCKcDHLOAH0=
APP_URL=http://localhost:8000
APP_LONGURL=localhost
APP_SHORTURL=local
DB_CONNECTION=travis
CACHE_DRIVER=array
SESSION_DRIVER=file
QUEUE_DRIVER=sync
SCOUT_DRIVER=pgsql

View file

@ -4,15 +4,15 @@ APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_TIMEZONE=UTC APP_TIMEZONE=UTC
APP_URL=https://example.com APP_URL=https://example.com
APP_LONGURL=example.com
APP_SHORTURL=examp.le
APP_LOCALE=en APP_LOCALE=en
APP_FALLBACK_LOCALE=en APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file APP_MAINTENANCE_DRIVER=file
APP_MAINTENANCE_STORE=database # APP_MAINTENANCE_STORE=database
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12 BCRYPT_ROUNDS=12
@ -39,7 +39,7 @@ FILESYSTEM_DISK=local
QUEUE_CONNECTION=database QUEUE_CONNECTION=database
CACHE_STORE=database CACHE_STORE=database
CACHE_PREFIX= # CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1 MEMCACHED_HOST=127.0.0.1
@ -49,6 +49,7 @@ REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379
MAIL_MAILER=log MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1 MAIL_HOST=127.0.0.1
MAIL_PORT=2525 MAIL_PORT=2525
MAIL_USERNAME=null MAIL_USERNAME=null

View file

@ -1,70 +0,0 @@
APP_NAME=Laravel
APP_ENV=testing
APP_KEY=SomeRandomString # Leave this
APP_DEBUG=false
APP_LOG_LEVEL=warning
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=jbukdev_testing
DB_USERNAME=postgres
DB_PASSWORD=postgres
BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
AWS_S3_KEY=your-key
AWS_S3_SECRET=your-secret
AWS_S3_REGION=region
AWS_S3_BUCKET=your-bucket
AWS_S3_URL=https://xxxxxxx.s3-region.amazonaws.com
APP_URL=https://example.com # This one is necessary
APP_LONGURL=example.com
APP_SHORTURL=examp.le
ADMIN_USER=admin # pick something better, this is used for `/admin`
ADMIN_PASS=password
DISPLAY_NAME="Joe Bloggs" # This is used for example in the header and titles
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN=
TWITTER_ACCESS_TOKEN_SECRET=
SCOUT_DRIVER=database
SCOUT_QUEUE=false
PIWIK=false
FATHOM_ID=
APP_TIMEZONE=UTC
APP_LANG=en
APP_LOG=daily
SECURE_SESSION_COOKIE=true
LOG_SLACK_WEBHOOK_URL=
FLARE_KEY=
FONT_LINK=
BRIDGY_MASTODON_TOKEN=

View file

@ -1,38 +0,0 @@
parserOptions:
sourceType: 'module'
ecmaVersion: 'latest'
extends: 'eslint:recommended'
env:
browser: true
es6: true
ignorePatterns:
- webpack.config.js
rules:
indent:
- error
- 2
linebreak-style:
- error
- unix
quotes:
- error
- single
semi:
- error
- always
no-console:
- error
- allow:
- warn
- error
no-await-in-loop:
- error
no-promise-executor-return:
- error
require-atomic-updates:
- error
max-nested-callbacks:
- error
- 3
prefer-promise-reject-errors:
- error

4
.gitattributes vendored
View file

@ -5,7 +5,3 @@
*.html diff=html *.html diff=html
*.md diff=markdown *.md diff=markdown
*.php diff=php *.php diff=php
/.github export-ignore
CHANGELOG.md export-ignore
.styleci.yml export-ignore

View file

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"

View file

@ -1,144 +0,0 @@
name: Deploy
on:
workflow_dispatch:
release:
types: [published]
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
environment: Hetzner
env:
repository: 'jonnybarnes/jonnybarnes.uk'
newReleaseName: '${{ github.run_id }}'
steps:
- name: 🌍 Set Environment Variables
run: |
echo "releasesDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/releases" >> $GITHUB_ENV
echo "persistentDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent" >> $GITHUB_ENV
echo "currentDir=${{ secrets.DEPLOYMENT_BASE_DIR }}/current" >> $GITHUB_ENV
- name: 🌎 Set Environment Variables Part 2
run: |
echo "newReleaseDir=${{ env.releasesDir }}/${{ env.newReleaseName }}" >> $GITHUB_ENV
- name: 🔄 Clone Repository
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOYMENT_HOST }}
port: ${{ secrets.DEPLOYMENT_PORT }}
username: ${{ secrets.DEPLOYMENT_USER }}
key: ${{ secrets.DEPLOYMENT_KEY }}
script: |
[ -d ${{ env.releasesDir }} ] || mkdir ${{ env.releasesDir }}
[ -d ${{ env.persistentDir }} ] || mkdir ${{ env.persistentDir }}
[ -d ${{ env.persistentDir }}/storage ] || mkdir ${{ env.persistentDir }}/storage
cd ${{ env.releasesDir }}
# Create new release directory
mkdir ${{ env.newReleaseDir }}
# Clone app
git clone --depth 1 --branch ${{ github.ref_name }} https://github.com/${{ env.repository }} ${{ env.newReleaseName }}
# Mark release
cd ${{ env.newReleaseDir }}
echo "${{ env.newReleaseName }}" > public/release-name.txt
# Fix cache directory permissions
sudo chown -R ${{ secrets.HTTP_USER }}:${{ secrets.HTTP_USER }} bootstrap/cache
- name: 🎵 Run Composer
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOYMENT_HOST }}
port: ${{ secrets.DEPLOYMENT_PORT }}
username: ${{ secrets.DEPLOYMENT_USER }}
key: ${{ secrets.DEPLOYMENT_KEY }}
script: |
cd ${{ env.newReleaseDir }}
composer install --prefer-dist --no-scripts --no-dev --no-progress --optimize-autoloader --quiet --no-interaction
- name: 🔗 Update Symlinks
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOYMENT_HOST }}
port: ${{ secrets.DEPLOYMENT_PORT }}
username: ${{ secrets.DEPLOYMENT_USER }}
key: ${{ secrets.DEPLOYMENT_KEY }}
script: |
# Import the environment config
cd ${{ env.newReleaseDir }};
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/.env .env;
# Remove the storage directory and replace with persistent data
rm -rf ${{ env.newReleaseDir }}/storage;
cd ${{ env.newReleaseDir }};
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/storage storage;
# Remove the public/profile-images directory and replace with persistent data
rm -rf ${{ env.newReleaseDir }}/public/assets/profile-images;
cd ${{ env.newReleaseDir }};
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/profile-images public/assets/profile-images;
# Add the persistent files data
cd ${{ env.newReleaseDir }};
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/files public/files;
# Add the persistent fonts data
cd ${{ env.newReleaseDir }};
ln -nfs ${{ secrets.DEPLOYMENT_BASE_DIR }}/persistent/fonts public/fonts;
- name: ✨ Optimize Installation
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOYMENT_HOST }}
port: ${{ secrets.DEPLOYMENT_PORT }}
username: ${{ secrets.DEPLOYMENT_USER }}
key: ${{ secrets.DEPLOYMENT_KEY }}
script: |
cd ${{ env.newReleaseDir }};
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan clear-compiled;
- name: 🙈 Migrate database
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOYMENT_HOST }}
port: ${{ secrets.DEPLOYMENT_PORT }}
username: ${{ secrets.DEPLOYMENT_USER }}
key: ${{ secrets.DEPLOYMENT_KEY }}
script: |
cd ${{ env.newReleaseDir }}
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan migrate --force
- name: 🙏 Bless release
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOYMENT_HOST }}
port: ${{ secrets.DEPLOYMENT_PORT }}
username: ${{ secrets.DEPLOYMENT_USER }}
key: ${{ secrets.DEPLOYMENT_KEY }}
script: |
ln -nfs ${{ env.newReleaseDir }} ${{ env.currentDir }};
cd ${{ env.newReleaseDir }}
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan horizon:terminate
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan config:cache
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan event:cache
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan route:cache
sudo runuser -u ${{ secrets.HTTP_USER }} -- php artisan view:cache
sudo systemctl restart php-fpm.service
sudo systemctl restart jbuk-horizon.service
- name: 🚾 Clean up old releases
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.DEPLOYMENT_HOST }}
port: ${{ secrets.DEPLOYMENT_PORT }}
username: ${{ secrets.DEPLOYMENT_USER }}
key: ${{ secrets.DEPLOYMENT_KEY }}
script: |
fd '.+' ${{ env.releasesDir }} -d 1 | head -n -3 | xargs -d "\n" -I'{}' sudo chown -R ${{ secrets.DEPLOYMENT_USER }}:${{ secrets.DEPLOYMENT_USER }} {}
fd '.+' ${{ env.releasesDir }} -d 1 | head -n -3 | xargs -d "\n" -I'{}' rm -rf {}

View file

@ -1,65 +0,0 @@
name: PHP Unit
on:
pull_request:
jobs:
phpunit:
runs-on: ubuntu-latest
name: PHPUnit test suite
services:
postgres:
image: postgres:latest
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: jbukdev_testing
ports:
- 5432:5432
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, intl, phpredis, imagick
coverage: xdebug
tools: phpunit
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.github', '.env');"
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-php-8.3-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-8.3-composer-
- name: Install Composer Dependencies
run: composer install --quiet --no-ansi --no-interaction --no-progress
- name: Generate Key
run: php artisan key:generate
- name: Setup Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Setup Database
run: php artisan migrate
- name: Execute PHPUnit Tests
run: vendor/bin/phpunit

View file

@ -1,38 +0,0 @@
name: Laravel Pint
on:
pull_request:
jobs:
pint:
runs-on: ubuntu-latest
name: Laravel Pint
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup PHP with pecl extensions
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'
- name: Get Composer Cache Directory
id: composer-cache
run: |
echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v3
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-composer-
- name: Install Composer Dependencies
run: composer install --quiet --no-ansi --no-interaction --no-progress
- name: Check Files with Laravel Pint
run: vendor/bin/pint --test

3
.gitignore vendored
View file

@ -4,7 +4,6 @@
/public/coverage /public/coverage
/public/hot /public/hot
/public/files /public/files
/public/fonts
/public/storage /public/storage
/storage/*.key /storage/*.key
/vendor /vendor
@ -21,3 +20,5 @@ yarn-error.log
/.idea /.idea
/.vscode /.vscode
ray.php ray.php
/public/gpg.key
/public/assets/img/favicon.png

View file

@ -1,9 +0,0 @@
php:
preset: laravel
disabled:
- no_unused_imports
finder:
not-name:
- index.php
js: true
css: true

View file

@ -0,0 +1,69 @@
<?php
namespace App\Console\Commands;
use App\Models\Media;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Storage;
class CopyMediaToLocal extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'app:copy-media-to-local';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Copy any historic media saved to S3 to the local filesystem';
/**
* Execute the console command.
*/
public function handle()
{
// Load all the Media records
$media = Media::all();
// Loop through each media record and copy the file from S3 to the local filesystem
foreach ($media as $mediaItem) {
$filename = $mediaItem->path;
$this->info('Processing: ' . $filename);
// If the file is already saved locally skip to next one
if (Storage::disk('local')->exists('public/' . $filename)) {
$this->info('File already exists locally, skipping');
continue;
}
// Copy the file from S3 to the local filesystem
if (! Storage::disk('s3')->exists($filename)) {
$this->error('File does not exist on S3');
continue;
}
$contents = Storage::disk('s3')->get($filename);
Storage::disk('local')->put('public/' . $filename, $contents);
// Copy -medium and -small versions if they exist
$filenameParts = explode('.', $filename);
$extension = array_pop($filenameParts);
$basename = trim(implode('.', $filenameParts), '.');
$mediumFilename = $basename . '-medium.' . $extension;
$smallFilename = $basename . '-small.' . $extension;
if (Storage::disk('s3')->exists($mediumFilename)) {
Storage::disk('local')->put('public/' . $mediumFilename, Storage::disk('s3')->get($mediumFilename));
}
if (Storage::disk('s3')->exists($smallFilename)) {
Storage::disk('local')->put('public/' . $smallFilename, Storage::disk('s3')->get($smallFilename));
}
}
}
}

View file

@ -8,8 +8,6 @@ use Illuminate\Support\Facades\DB;
/** /**
* @codeCoverageIgnore * @codeCoverageIgnore
*
* @psalm-suppress UnusedClass
*/ */
class MigratePlaceDataFromPostgis extends Command class MigratePlaceDataFromPostgis extends Command
{ {

View file

@ -9,9 +9,6 @@ use Illuminate\Console\Command;
use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\FileSystem\FileSystem; use Illuminate\FileSystem\FileSystem;
/**
* @psalm-suppress UnusedClass
*/
class ParseCachedWebMentions extends Command class ParseCachedWebMentions extends Command
{ {
/** /**
@ -37,7 +34,7 @@ class ParseCachedWebMentions extends Command
{ {
$htmlFiles = $filesystem->allFiles(storage_path() . '/HTML'); $htmlFiles = $filesystem->allFiles(storage_path() . '/HTML');
foreach ($htmlFiles as $file) { foreach ($htmlFiles as $file) {
if ($file->getExtension() !== 'backup') { //we dont want to parse `.backup` files if ($file->getExtension() !== 'backup') { // we dont want to parse `.backup` files
$filepath = $file->getPathname(); $filepath = $file->getPathname();
$this->info('Loading HTML from: ' . $filepath); $this->info('Loading HTML from: ' . $filepath);
$html = $filesystem->get($filepath); $html = $filesystem->get($filepath);

View file

@ -8,9 +8,6 @@ use App\Jobs\DownloadWebMention;
use App\Models\WebMention; use App\Models\WebMention;
use Illuminate\Console\Command; use Illuminate\Console\Command;
/**
* @psalm-suppress UnusedClass
*/
class ReDownloadWebMentions extends Command class ReDownloadWebMentions extends Command
{ {
/** /**

View file

@ -0,0 +1,36 @@
<?php
namespace App\Console\Commands;
use App\Models\Note;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class UpdateWebmentionsRelationship extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'webmentions:update-model-relationship';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Update webmentions to relate to the correct note model class';
/**
* Execute the console command.
*/
public function handle()
{
DB::table('webmentions')
->where('commentable_type', '=', 'App\Model\Note')
->update(['commentable_type' => Note::class]);
$this->info('All webmentions updated to relate to the correct note model class');
}
}

View file

@ -2,6 +2,4 @@
namespace App\Exceptions; namespace App\Exceptions;
class InternetArchiveException extends \Exception class InternetArchiveException extends \Exception {}
{
}

View file

@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Exceptions;
class InvalidTokenScopeException extends \Exception {}

View file

@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
namespace App\Exceptions;
class MicropubHandlerException extends \Exception {}

View file

@ -6,5 +6,5 @@ use Exception;
class RemoteContentNotFoundException extends Exception class RemoteContentNotFoundException extends Exception
{ {
//used when guzzle cant find the remote content // used when guzzle cant find the remote content
} }

View file

@ -9,9 +9,6 @@ use App\Models\Article;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class ArticlesController extends Controller class ArticlesController extends Controller
{ {
public function index(): View public function index(): View
@ -30,7 +27,7 @@ class ArticlesController extends Controller
public function store(): RedirectResponse public function store(): RedirectResponse
{ {
//if a `.md` is attached use that for the main content. // if a `.md` is attached use that for the main content.
if (request()->hasFile('article')) { if (request()->hasFile('article')) {
$file = request()->file('article')->openFile(); $file = request()->file('article')->openFile();
$content = $file->fread($file->getSize()); $content = $file->fread($file->getSize());

View file

@ -10,9 +10,6 @@ use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class BioController extends Controller class BioController extends Controller
{ {
public function show(): View public function show(): View

View file

@ -9,9 +9,6 @@ use App\Models\MicropubClient;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class ClientsController extends Controller class ClientsController extends Controller
{ {
/** /**

View file

@ -12,9 +12,6 @@ use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class ContactsController extends Controller class ContactsController extends Controller
{ {
/** /**
@ -40,7 +37,7 @@ class ContactsController extends Controller
*/ */
public function store(): RedirectResponse public function store(): RedirectResponse
{ {
$contact = new Contact(); $contact = new Contact;
$contact->name = request()->input('name'); $contact->name = request()->input('name');
$contact->nick = request()->input('nick'); $contact->nick = request()->input('nick');
$contact->homepage = request()->input('homepage'); $contact->homepage = request()->input('homepage');
@ -79,7 +76,7 @@ class ContactsController extends Controller
if (request()->hasFile('avatar') && (request()->input('homepage') != '')) { if (request()->hasFile('avatar') && (request()->input('homepage') != '')) {
$dir = parse_url(request()->input('homepage'), PHP_URL_HOST); $dir = parse_url(request()->input('homepage'), PHP_URL_HOST);
$destination = public_path() . '/assets/profile-images/' . $dir; $destination = public_path() . '/assets/profile-images/' . $dir;
$filesystem = new Filesystem(); $filesystem = new Filesystem;
if ($filesystem->isDirectory($destination) === false) { if ($filesystem->isDirectory($destination) === false) {
$filesystem->makeDirectory($destination); $filesystem->makeDirectory($destination);
} }
@ -139,7 +136,7 @@ class ContactsController extends Controller
} }
if ($avatar !== null) { if ($avatar !== null) {
$directory = public_path() . '/assets/profile-images/' . parse_url($contact->homepage, PHP_URL_HOST); $directory = public_path() . '/assets/profile-images/' . parse_url($contact->homepage, PHP_URL_HOST);
$filesystem = new Filesystem(); $filesystem = new Filesystem;
if ($filesystem->isDirectory($directory) === false) { if ($filesystem->isDirectory($directory) === false) {
$filesystem->makeDirectory($directory); $filesystem->makeDirectory($directory);
} }

View file

@ -7,9 +7,6 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class HomeController extends Controller class HomeController extends Controller
{ {
/** /**

View file

@ -10,9 +10,6 @@ use App\Models\Like;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class LikesController extends Controller class LikesController extends Controller
{ {
/** /**

View file

@ -11,9 +11,6 @@ use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class NotesController extends Controller class NotesController extends Controller
{ {
/** /**
@ -67,7 +64,7 @@ class NotesController extends Controller
*/ */
public function update(int $noteId): RedirectResponse public function update(int $noteId): RedirectResponse
{ {
//update note data // update note data
$note = Note::findOrFail($noteId); $note = Note::findOrFail($noteId);
$note->note = request()->input('content'); $note->note = request()->input('content');
$note->in_reply_to = request()->input('in-reply-to'); $note->in_reply_to = request()->input('in-reply-to');

View file

@ -18,8 +18,8 @@ use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\View\View; use Illuminate\View\View;
use ParagonIE\ConstantTime\Base64UrlSafe; use ParagonIE\ConstantTime\Base64UrlSafe;
use Random\RandomException;
use Throwable; use Throwable;
use Webauthn\AttestationStatement\AttestationObjectLoader;
use Webauthn\AttestationStatement\AttestationStatementSupportManager; use Webauthn\AttestationStatement\AttestationStatementSupportManager;
use Webauthn\AttestationStatement\NoneAttestationStatementSupport; use Webauthn\AttestationStatement\NoneAttestationStatementSupport;
use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler; use Webauthn\AuthenticationExtensions\ExtensionOutputCheckerHandler;
@ -28,18 +28,17 @@ use Webauthn\AuthenticatorAssertionResponseValidator;
use Webauthn\AuthenticatorAttestationResponse; use Webauthn\AuthenticatorAttestationResponse;
use Webauthn\AuthenticatorAttestationResponseValidator; use Webauthn\AuthenticatorAttestationResponseValidator;
use Webauthn\AuthenticatorSelectionCriteria; use Webauthn\AuthenticatorSelectionCriteria;
use Webauthn\CeremonyStep\CeremonyStepManagerFactory;
use Webauthn\Denormalizer\WebauthnSerializerFactory;
use Webauthn\Exception\WebauthnException; use Webauthn\Exception\WebauthnException;
use Webauthn\PublicKeyCredential;
use Webauthn\PublicKeyCredentialCreationOptions; use Webauthn\PublicKeyCredentialCreationOptions;
use Webauthn\PublicKeyCredentialLoader;
use Webauthn\PublicKeyCredentialParameters; use Webauthn\PublicKeyCredentialParameters;
use Webauthn\PublicKeyCredentialRequestOptions; use Webauthn\PublicKeyCredentialRequestOptions;
use Webauthn\PublicKeyCredentialRpEntity; use Webauthn\PublicKeyCredentialRpEntity;
use Webauthn\PublicKeyCredentialSource; use Webauthn\PublicKeyCredentialSource;
use Webauthn\PublicKeyCredentialUserEntity; use Webauthn\PublicKeyCredentialUserEntity;
/**
* @psalm-suppress UnusedClass
*/
class PasskeysController extends Controller class PasskeysController extends Controller
{ {
public function index(): View public function index(): View
@ -51,22 +50,26 @@ class PasskeysController extends Controller
return view('admin.passkeys.index', compact('passkeys')); return view('admin.passkeys.index', compact('passkeys'));
} }
public function getCreateOptions(): JsonResponse /**
* @throws RandomException
* @throws \JsonException
*/
public function getCreateOptions(Request $request): JsonResponse
{ {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
// RP Entity i.e. the application // RP Entity i.e. the application
$rpEntity = PublicKeyCredentialRpEntity::create( $rpEntity = PublicKeyCredentialRpEntity::create(
config('app.name'), name: config('app.name'),
config('url.longurl'), id: config('app.url'),
); );
// User Entity // User Entity
$userEntity = PublicKeyCredentialUserEntity::create( $userEntity = PublicKeyCredentialUserEntity::create(
$user->name, name: $user->name,
(string) $user->id, id: (string) $user->id,
$user->name, displayName: $user->name,
); );
// Challenge // Challenge
@ -84,70 +87,100 @@ class PasskeysController extends Controller
$authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create( $authenticatorSelectionCriteria = AuthenticatorSelectionCriteria::create(
userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED, userVerification: AuthenticatorSelectionCriteria::USER_VERIFICATION_REQUIREMENT_REQUIRED,
residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED, residentKey: AuthenticatorSelectionCriteria::RESIDENT_KEY_REQUIREMENT_REQUIRED,
requireResidentKey: true,
); );
$options = PublicKeyCredentialCreationOptions::create( $publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::create(
$rpEntity, rp: $rpEntity,
$userEntity, user: $userEntity,
$challenge, challenge: $challenge,
$pubKeyCredParams, pubKeyCredParams: $pubKeyCredParams,
authenticatorSelection: $authenticatorSelectionCriteria, authenticatorSelection: $authenticatorSelectionCriteria,
attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE attestation: PublicKeyCredentialCreationOptions::ATTESTATION_CONVEYANCE_PREFERENCE_NONE
); );
$options = json_encode($options, JSON_THROW_ON_ERROR); $attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$webauthnSerializerFactory = new WebauthnSerializerFactory(
attestationStatementSupportManager: $attestationStatementSupportManager
);
$webauthnSerializer = $webauthnSerializerFactory->create();
$publicKeyCredentialCreationOptions = $webauthnSerializer->serialize(
data: $publicKeyCredentialCreationOptions,
format: 'json'
);
session(['create_options' => $options]); $request->session()->put('create_options', $publicKeyCredentialCreationOptions);
return JsonResponse::fromJsonString($options); return JsonResponse::fromJsonString($publicKeyCredentialCreationOptions);
} }
/**
* @throws Throwable
* @throws WebauthnException
* @throws \JsonException
*/
public function create(Request $request): JsonResponse public function create(Request $request): JsonResponse
{ {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
$publicKeyCredentialCreationOptionsData = session('create_options'); $publicKeyCredentialCreationOptionsData = session('create_options');
// Unset session data to mitigate replay attacks
$request->session()->forget('create_options');
if (empty($publicKeyCredentialCreationOptionsData)) { if (empty($publicKeyCredentialCreationOptionsData)) {
throw new WebAuthnException('No public key credential request options found'); throw new WebAuthnException('No public key credential request options found');
} }
$publicKeyCredentialCreationOptions = PublicKeyCredentialCreationOptions::createFromString($publicKeyCredentialCreationOptionsData);
// Unset session data to mitigate replay attacks $attestationStatementSupportManager = new AttestationStatementSupportManager;
session()->forget('create_options'); $attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$webauthnSerializerFactory = new WebauthnSerializerFactory(
attestationStatementSupportManager: $attestationStatementSupportManager
);
$webauthnSerializer = $webauthnSerializerFactory->create();
$attestationSupportManager = AttestationStatementSupportManager::create(); $publicKeyCredential = $webauthnSerializer->deserialize(
$attestationSupportManager->add(NoneAttestationStatementSupport::create()); json_encode($request->all(), JSON_THROW_ON_ERROR),
$attestationObjectLoader = AttestationObjectLoader::create($attestationSupportManager); PublicKeyCredential::class,
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader); 'json'
);
$publicKeyCredential = $publicKeyCredentialLoader->load(json_encode($request->all(), JSON_THROW_ON_ERROR));
if (! $publicKeyCredential->response instanceof AuthenticatorAttestationResponse) { if (! $publicKeyCredential->response instanceof AuthenticatorAttestationResponse) {
throw new WebAuthnException('Invalid response type'); throw new WebAuthnException('Invalid response type');
} }
$attestationStatementSupportManager = AttestationStatementSupportManager::create(); $algorithmManager = new Manager;
$attestationStatementSupportManager->add(NoneAttestationStatementSupport::create()); $algorithmManager->add(new Ed25519);
$algorithmManager->add(new ES256);
$algorithmManager->add(new RS256);
$ceremonyStepManagerFactory = new CeremonyStepManagerFactory;
$ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager);
$ceremonyStepManagerFactory->setAttestationStatementSupportManager(
$attestationStatementSupportManager
);
$ceremonyStepManagerFactory->setExtensionOutputCheckerHandler(
ExtensionOutputCheckerHandler::create()
);
$allowedOrigins = [];
if (App::environment('local', 'development')) {
$allowedOrigins = [config('app.url')];
}
$ceremonyStepManagerFactory->setAllowedOrigins($allowedOrigins);
$authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create( $authenticatorAttestationResponseValidator = AuthenticatorAttestationResponseValidator::create(
attestationStatementSupportManager: $attestationStatementSupportManager, ceremonyStepManager: $ceremonyStepManagerFactory->creationCeremony()
publicKeyCredentialSourceRepository: null,
tokenBindingHandler: null,
extensionOutputCheckerHandler: ExtensionOutputCheckerHandler::create(),
); );
$securedRelyingPartyId = []; $publicKeyCredentialCreationOptions = $webauthnSerializer->deserialize(
if (App::environment('local', 'development')) { $publicKeyCredentialCreationOptionsData,
$securedRelyingPartyId = [config('url.longurl')]; PublicKeyCredentialCreationOptions::class,
} 'json'
);
$publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check( $publicKeyCredentialSource = $authenticatorAttestationResponseValidator->check(
authenticatorAttestationResponse: $publicKeyCredential->response, authenticatorAttestationResponse: $publicKeyCredential->response,
publicKeyCredentialCreationOptions: $publicKeyCredentialCreationOptions, publicKeyCredentialCreationOptions: $publicKeyCredentialCreationOptions,
request: config('url.longurl'), host: config('app.url')
securedRelyingPartyId: $securedRelyingPartyId,
); );
$user->passkey()->create([ $user->passkey()->create([
@ -161,24 +194,37 @@ class PasskeysController extends Controller
]); ]);
} }
public function getRequestOptions(): JsonResponse /**
* @throws RandomException
* @throws \JsonException
*/
public function getRequestOptions(Request $request): JsonResponse
{ {
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create( $publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::create(
challenge: random_bytes(16), challenge: random_bytes(16),
userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED userVerification: PublicKeyCredentialRequestOptions::USER_VERIFICATION_REQUIREMENT_REQUIRED
); );
$publicKeyCredentialRequestOptions = json_encode($publicKeyCredentialRequestOptions, JSON_THROW_ON_ERROR); $attestationStatementSupportManager = AttestationStatementSupportManager::create();
$attestationStatementSupportManager->add(NoneAttestationStatementSupport::create());
$factory = new WebauthnSerializerFactory(
attestationStatementSupportManager: $attestationStatementSupportManager
);
$serializer = $factory->create();
$publicKeyCredentialRequestOptions = $serializer->serialize(data: $publicKeyCredentialRequestOptions, format: 'json');
session(['request_options' => $publicKeyCredentialRequestOptions]); $request->session()->put('request_options', $publicKeyCredentialRequestOptions);
return JsonResponse::fromJsonString($publicKeyCredentialRequestOptions); return JsonResponse::fromJsonString($publicKeyCredentialRequestOptions);
} }
/**
* @throws \JsonException
*/
public function login(Request $request): JsonResponse public function login(Request $request): JsonResponse
{ {
$requestOptions = session('request_options'); $requestOptions = session('request_options');
session()->forget('request_options'); $request->session()->forget('request_options');
if (empty($requestOptions)) { if (empty($requestOptions)) {
return response()->json([ return response()->json([
@ -187,14 +233,19 @@ class PasskeysController extends Controller
], 400); ], 400);
} }
$publicKeyCredentialRequestOptions = PublicKeyCredentialRequestOptions::createFromString($requestOptions); $attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$attestationSupportManager = AttestationStatementSupportManager::create(); $webauthnSerializerFactory = new WebauthnSerializerFactory(
$attestationSupportManager->add(NoneAttestationStatementSupport::create()); attestationStatementSupportManager: $attestationStatementSupportManager
$attestationObjectLoader = AttestationObjectLoader::create($attestationSupportManager); );
$publicKeyCredentialLoader = PublicKeyCredentialLoader::create($attestationObjectLoader); $webauthnSerializer = $webauthnSerializerFactory->create();
$publicKeyCredential = $publicKeyCredentialLoader->load(json_encode($request->all(), JSON_THROW_ON_ERROR)); $publicKeyCredential = $webauthnSerializer->deserialize(
json_encode($request->all(), JSON_THROW_ON_ERROR),
PublicKeyCredential::class,
'json'
);
if (! $publicKeyCredential->response instanceof AuthenticatorAssertionResponse) { if (! $publicKeyCredential->response instanceof AuthenticatorAssertionResponse) {
return response()->json([ return response()->json([
@ -211,33 +262,51 @@ class PasskeysController extends Controller
], 404); ], 404);
} }
$credential = PublicKeyCredentialSource::createFromArray(json_decode($passkey->passkey, true, 512, JSON_THROW_ON_ERROR)); $publicKeyCredentialSource = $webauthnSerializer->deserialize(
$passkey->passkey,
$algorithmManager = Manager::create(); PublicKeyCredentialSource::class,
$algorithmManager->add(new Ed25519()); 'json'
$algorithmManager->add(new ES256());
$algorithmManager->add(new RS256());
$authenticatorAssertionResponseValidator = new AuthenticatorAssertionResponseValidator(
publicKeyCredentialSourceRepository: null,
tokenBindingHandler: null,
extensionOutputCheckerHandler: ExtensionOutputCheckerHandler::create(),
algorithmManager: $algorithmManager,
); );
$securedRelyingPartyId = []; $algorithmManager = new Manager;
$algorithmManager->add(new Ed25519);
$algorithmManager->add(new ES256);
$algorithmManager->add(new RS256);
$attestationStatementSupportManager = new AttestationStatementSupportManager;
$attestationStatementSupportManager->add(new NoneAttestationStatementSupport);
$ceremonyStepManagerFactory = new CeremonyStepManagerFactory;
$ceremonyStepManagerFactory->setAlgorithmManager($algorithmManager);
$ceremonyStepManagerFactory->setAttestationStatementSupportManager(
$attestationStatementSupportManager
);
$ceremonyStepManagerFactory->setExtensionOutputCheckerHandler(
ExtensionOutputCheckerHandler::create()
);
$allowedOrigins = [];
if (App::environment('local', 'development')) { if (App::environment('local', 'development')) {
$securedRelyingPartyId = [config('url.longurl')]; $allowedOrigins = [config('app.url')];
} }
$ceremonyStepManagerFactory->setAllowedOrigins($allowedOrigins);
$authenticatorAssertionResponseValidator = AuthenticatorAssertionResponseValidator::create(
ceremonyStepManager: $ceremonyStepManagerFactory->requestCeremony()
);
$publicKeyCredentialRequestOptions = $webauthnSerializer->deserialize(
$requestOptions,
PublicKeyCredentialRequestOptions::class,
'json'
);
try { try {
$authenticatorAssertionResponseValidator->check( $authenticatorAssertionResponseValidator->check(
credentialId: $credential, publicKeyCredentialSource: $publicKeyCredentialSource,
authenticatorAssertionResponse: $publicKeyCredential->response, authenticatorAssertionResponse: $publicKeyCredential->response,
publicKeyCredentialRequestOptions: $publicKeyCredentialRequestOptions, publicKeyCredentialRequestOptions: $publicKeyCredentialRequestOptions,
request: config('url.longurl'), host: config('app.url'),
userHandle: null, userHandle: null,
securedRelyingPartyId: $securedRelyingPartyId,
); );
} catch (Throwable) { } catch (Throwable) {
return response()->json([ return response()->json([

View file

@ -10,9 +10,6 @@ use App\Services\PlaceService;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class PlacesController extends Controller class PlacesController extends Controller
{ {
protected PlaceService $placeService; protected PlaceService $placeService;

View file

@ -10,9 +10,6 @@ use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class SyndicationTargetsController extends Controller class SyndicationTargetsController extends Controller
{ {
/** /**

View file

@ -10,9 +10,6 @@ use Illuminate\Http\RedirectResponse;
use Illuminate\View\View; use Illuminate\View\View;
use Jonnybarnes\IndieWeb\Numbers; use Jonnybarnes\IndieWeb\Numbers;
/**
* @psalm-suppress UnusedClass
*/
class ArticlesController extends Controller class ArticlesController extends Controller
{ {
/** /**

View file

@ -9,9 +9,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class AuthController extends Controller class AuthController extends Controller
{ {
/** /**

View file

@ -7,9 +7,6 @@ namespace App\Http\Controllers;
use App\Models\Bookmark; use App\Models\Bookmark;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class BookmarksController extends Controller class BookmarksController extends Controller
{ {
/** /**

View file

@ -8,9 +8,6 @@ use App\Models\Contact;
use Illuminate\Filesystem\Filesystem; use Illuminate\Filesystem\Filesystem;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class ContactsController extends Controller class ContactsController extends Controller
{ {
/** /**
@ -18,7 +15,7 @@ class ContactsController extends Controller
*/ */
public function index(): View public function index(): View
{ {
$filesystem = new Filesystem(); $filesystem = new Filesystem;
$contacts = Contact::all(); $contacts = Contact::all();
foreach ($contacts as $contact) { foreach ($contacts as $contact) {
$contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST); $contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST);
@ -40,7 +37,7 @@ class ContactsController extends Controller
$contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST); $contact->homepageHost = parse_url($contact->homepage, PHP_URL_HOST);
$file = public_path() . '/assets/profile-images/' . $contact->homepageHost . '/image'; $file = public_path() . '/assets/profile-images/' . $contact->homepageHost . '/image';
$filesystem = new Filesystem(); $filesystem = new Filesystem;
$image = ($filesystem->exists($file)) ? $image = ($filesystem->exists($file)) ?
'/assets/profile-images/' . $contact->homepageHost . '/image' '/assets/profile-images/' . $contact->homepageHost . '/image'
: :

View file

@ -9,9 +9,6 @@ use App\Models\Note;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response; use Illuminate\Http\Response;
/**
* @psalm-suppress UnusedClass
*/
class FeedsController extends Controller class FeedsController extends Controller
{ {
/** /**
@ -122,8 +119,8 @@ class FeedsController extends Controller
foreach ($notes as $key => $note) { foreach ($notes as $key => $note) {
$data['items'][$key] = [ $data['items'][$key] = [
'id' => $note->longurl, 'id' => $note->uri,
'url' => $note->longurl, 'url' => $note->uri,
'content_text' => $note->content, 'content_text' => $note->content,
'date_published' => $note->created_at->tz('UTC')->toRfc3339String(), 'date_published' => $note->created_at->tz('UTC')->toRfc3339String(),
'date_modified' => $note->updated_at->tz('UTC')->toRfc3339String(), 'date_modified' => $note->updated_at->tz('UTC')->toRfc3339String(),
@ -164,7 +161,7 @@ class FeedsController extends Controller
'author' => [ 'author' => [
'type' => 'card', 'type' => 'card',
'name' => config('user.display_name'), 'name' => config('user.display_name'),
'url' => config('url.longurl'), 'url' => config('app.url'),
], ],
'children' => $items, 'children' => $items,
], 200, [ ], 200, [
@ -183,8 +180,8 @@ class FeedsController extends Controller
$items[] = [ $items[] = [
'type' => 'entry', 'type' => 'entry',
'published' => $note->created_at, 'published' => $note->created_at,
'uid' => $note->longurl, 'uid' => $note->uri,
'url' => $note->longurl, 'url' => $note->uri,
'content' => [ 'content' => [
'text' => $note->getRawOriginal('note'), 'text' => $note->getRawOriginal('note'),
'html' => $note->note, 'html' => $note->note,
@ -200,7 +197,7 @@ class FeedsController extends Controller
'author' => [ 'author' => [
'type' => 'card', 'type' => 'card',
'name' => config('user.display_name'), 'name' => config('user.display_name'),
'url' => config('url.longurl'), 'url' => config('app.url'),
], ],
'children' => $items, 'children' => $items,
], 200, [ ], 200, [

View file

@ -10,9 +10,6 @@ use App\Models\Note;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class FrontPageController extends Controller class FrontPageController extends Controller
{ {
/** /**

View file

@ -0,0 +1,327 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Services\TokenService;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Uri;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Validator;
use Illuminate\View\View;
use Random\RandomException;
use SodiumException;
class IndieAuthController extends Controller
{
public function indieAuthMetadataEndpoint(): JsonResponse
{
return response()->json([
'issuer' => config('app.url'),
'authorization_endpoint' => route('indieauth.start'),
'token_endpoint' => route('indieauth.token'),
'code_challenge_methods_supported' => ['S256'],
// 'introspection_endpoint' => route('indieauth.introspection'),
// 'introspection_endpoint_auth_methods_supported' => ['none'],
]);
}
/**
* Process a GET request to the IndieAuth endpoint.
*
* This is the first step in the IndieAuth flow, where the client app sends the user to the IndieAuth endpoint.
*/
public function start(Request $request): View
{
// First check all required params are present
$validator = Validator::make($request->all(), [
'response_type' => 'required:string',
'client_id' => 'required',
'redirect_uri' => 'required',
'state' => 'required',
'code_challenge' => 'required:string',
'code_challenge_method' => 'required:string',
], [
'response_type' => 'response_type is required',
'client_id.required' => 'client_id is required to display which app is asking for authentication',
'redirect_uri.required' => 'redirect_uri is required so we can progress successful requests',
'state.required' => 'state is required',
'code_challenge.required' => 'code_challenge is required',
'code_challenge_method.required' => 'code_challenge_method is required',
]);
if ($validator->fails()) {
return view('indieauth.error')->withErrors($validator);
}
if ($request->get('response_type') !== 'code') {
return view('indieauth.error')->withErrors(['response_type' => 'only a response_type of "code" is supported']);
}
if (mb_strtoupper($request->get('code_challenge_method')) !== 'S256') {
return view('indieauth.error')->withErrors(['code_challenge_method' => 'only a code_challenge_method of "S256" is supported']);
}
if (! $this->isValidRedirectUri($request->get('client_id'), $request->get('redirect_uri'))) {
return view('indieauth.error')->withErrors(['redirect_uri' => 'redirect_uri is not valid for this client_id']);
}
$scopes = $request->get('scope', '');
$scopes = explode(' ', $scopes);
return view('indieauth.start', [
'me' => $request->get('me'),
'client_id' => $request->get('client_id'),
'redirect_uri' => $request->get('redirect_uri'),
'state' => $request->get('state'),
'scopes' => $scopes,
'code_challenge' => $request->get('code_challenge'),
'code_challenge_method' => $request->get('code_challenge_method'),
]);
}
/**
* Confirm an IndieAuth approval request.
*
* Generates an auth code and redirects the user back to the client app.
*
* @throws RandomException
*/
public function confirm(Request $request): RedirectResponse
{
$authCode = bin2hex(random_bytes(16));
$cacheKey = hash('xxh3', $request->get('client_id'));
$indieAuthRequestData = [
'code_challenge' => $request->get('code_challenge'),
'code_challenge_method' => $request->get('code_challenge_method'),
'client_id' => $request->get('client_id'),
'redirect_uri' => $request->get('redirect_uri'),
'auth_code' => $authCode,
'scope' => implode(' ', $request->get('scope', '')),
];
Cache::put($cacheKey, $indieAuthRequestData, now()->addMinutes(10));
$redirectUri = new Uri($request->get('redirect_uri'));
$redirectUri = Uri::withQueryValues($redirectUri, [
'code' => $authCode,
'state' => $request->get('state'),
'iss' => config('app.url'),
]);
return redirect()->away($redirectUri);
}
/**
* Process a POST request to the IndieAuth auth endpoint.
*
* This is one possible second step in the IndieAuth flow, where the client app sends the auth code to the IndieAuth
* endpoint. As it is to the auth endpoint we return profile information. A similar request can be made to the token
* endpoint to get an access token.
*/
public function processCodeExchange(Request $request): JsonResponse
{
$invalidCodeResponse = $this->validateAuthorizationCode($request);
if ($invalidCodeResponse instanceof JsonResponse) {
return $invalidCodeResponse;
}
return response()->json([
'me' => config('app.url'),
]);
}
/**
* Process a POST request to the IndieAuth token endpoint.
*
* This is another possible second step in the IndieAuth flow, where the client app sends the auth code to the
* IndieAuth token endpoint. As it is to the token endpoint we return an access token.
*
* @throws SodiumException
*/
public function processTokenRequest(Request $request): JsonResponse
{
$indieAuthData = $this->validateAuthorizationCode($request);
if ($indieAuthData instanceof JsonResponse) {
return $indieAuthData;
}
if ($indieAuthData['scope'] === '') {
return response()->json(['errors' => [
'scope' => [
'The scope property must be non-empty for an access token to be issued.',
],
]], 400);
}
$tokenData = [
'me' => config('app.url'),
'client_id' => $request->get('client_id'),
'scope' => $indieAuthData['scope'],
];
$tokenService = resolve(TokenService::class);
$token = $tokenService->getNewToken($tokenData);
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
'scope' => $indieAuthData['scope'],
'me' => config('app.url'),
]);
}
protected function isValidRedirectUri(string $clientId, string $redirectUri): bool
{
// If client_id is not a valid URL, then it's not valid
$clientIdParsed = \Mf2\parseUriToComponents($clientId);
if (! isset($clientIdParsed['authority'])) {
return false;
}
// If redirect_uri is not a valid URL, then it's not valid
$redirectUriParsed = \Mf2\parseUriToComponents($redirectUri);
if (! isset($redirectUriParsed['authority'])) {
return false;
}
// If client_id and redirect_uri are the same host, then it's valid
if ($clientIdParsed['authority'] === $redirectUriParsed['authority']) {
return true;
}
// Otherwise we need to check the redirect_uri is in the client_id's redirect_uris
$guzzle = resolve(Client::class);
try {
$clientInfo = $guzzle->get($clientId);
} catch (Exception) {
return false;
}
$clientInfoParsed = \Mf2\parse($clientInfo->getBody()->getContents(), $clientId);
$redirectUris = $clientInfoParsed['rels']['redirect_uri'] ?? [];
return in_array($redirectUri, $redirectUris, true);
}
/**
* @throws SodiumException
*/
protected function validateAuthorizationCode(Request $request): JsonResponse|array
{
// First check all the data is present
$validator = Validator::make($request->all(), [
'grant_type' => 'required:string',
'code' => 'required:string',
'client_id' => 'required',
'redirect_uri' => 'required',
'code_verifier' => 'required',
]);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 400);
}
if ($request->get('grant_type') !== 'authorization_code') {
return response()->json(['errors' => [
'grant_type' => [
'Only a grant type of "authorization_code" is supported.',
],
]], 400);
}
// Check cache for auth code
$cacheKey = hash('xxh3', $request->get('client_id'));
$indieAuthRequestData = Cache::pull($cacheKey);
if ($indieAuthRequestData === null) {
return response()->json(['errors' => [
'code' => [
'The code is invalid.',
],
]], 404);
}
// Check the IndieAuth code
if (! array_key_exists('auth_code', $indieAuthRequestData)) {
return response()->json(['errors' => [
'code' => [
'The code is invalid.',
],
]], 400);
}
if ($indieAuthRequestData['auth_code'] !== $request->get('code')) {
return response()->json(['errors' => [
'code' => [
'The code is invalid.',
],
]], 400);
}
// Check code verifier
if (! array_key_exists('code_challenge', $indieAuthRequestData)) {
return response()->json(['errors' => [
'code_verifier' => [
'The code verifier is invalid.',
],
]], 400);
}
if (! hash_equals(
$indieAuthRequestData['code_challenge'],
sodium_bin2base64(
hash('sha256', $request->get('code_verifier'), true),
SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING
)
)) {
return response()->json(['errors' => [
'code_verifier' => [
'The code verifier is invalid.',
],
]], 400);
}
// Check redirect_uri
if (! array_key_exists('redirect_uri', $indieAuthRequestData)) {
return response()->json(['errors' => [
'redirect_uri' => [
'The redirect uri is invalid.',
],
]], 400);
}
if ($indieAuthRequestData['redirect_uri'] !== $request->get('redirect_uri')) {
return response()->json(['errors' => [
'redirect_uri' => [
'The redirect uri is invalid.',
],
]], 400);
}
// Check client_id
if (! array_key_exists('client_id', $indieAuthRequestData)) {
return response()->json(['errors' => [
'client_id' => [
'The client id is invalid.',
],
]], 400);
}
if ($indieAuthRequestData['client_id'] !== $request->get('client_id')) {
return response()->json(['errors' => [
'client_id' => [
'The client id is invalid.',
],
]], 400);
}
return $indieAuthRequestData;
}
}

View file

@ -7,9 +7,6 @@ namespace App\Http\Controllers;
use App\Models\Like; use App\Models\Like;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class LikesController extends Controller class LikesController extends Controller
{ {
/** /**

View file

@ -4,110 +4,73 @@ declare(strict_types=1);
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Http\Responses\MicropubResponses; use App\Exceptions\InvalidTokenScopeException;
use App\Exceptions\MicropubHandlerException;
use App\Http\Requests\MicropubRequest;
use App\Models\Place; use App\Models\Place;
use App\Models\SyndicationTarget; use App\Models\SyndicationTarget;
use App\Services\Micropub\HCardService; use App\Services\Micropub\MicropubHandlerRegistry;
use App\Services\Micropub\HEntryService;
use App\Services\Micropub\UpdateService;
use App\Services\TokenService;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Lcobucci\JWT\Encoding\CannotDecodeContent; use Lcobucci\JWT\Token;
use Lcobucci\JWT\Token\InvalidTokenStructure;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
/**
* @psalm-suppress UnusedClass
*/
class MicropubController extends Controller class MicropubController extends Controller
{ {
protected TokenService $tokenService; protected MicropubHandlerRegistry $handlerRegistry;
protected HEntryService $hentryService; public function __construct(MicropubHandlerRegistry $handlerRegistry)
{
protected HCardService $hcardService; $this->handlerRegistry = $handlerRegistry;
protected UpdateService $updateService;
public function __construct(
TokenService $tokenService,
HEntryService $hentryService,
HCardService $hcardService,
UpdateService $updateService
) {
$this->tokenService = $tokenService;
$this->hentryService = $hentryService;
$this->hcardService = $hcardService;
$this->updateService = $updateService;
} }
/** /**
* This function receives an API request, verifies the authenticity * Respond to a POST request to the micropub endpoint.
* then passes over the info to the relevant Service class. *
* The request is initially processed by the MicropubRequest form request
* class. The normalizes the data, so we can pass it into the handlers for
* the different micropub requests, h-entry or h-card, for example.
*/ */
public function post(Request $request): JsonResponse public function post(MicropubRequest $request): JsonResponse
{ {
$type = $request->getType();
if (! $type) {
return response()->json([
'error' => 'invalid_request',
'error_description' => 'Microformat object type is missing, for example: h-entry or h-card',
], 400);
}
try { try {
$tokenData = $this->tokenService->validateToken($request->input('access_token')); $handler = $this->handlerRegistry->getHandler($type);
} catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) { $result = $handler->handle($request->getMicropubData());
$micropubResponses = new MicropubResponses();
return $micropubResponses->invalidTokenResponse();
}
if ($tokenData->claims()->has('scope') === false) {
$micropubResponses = new MicropubResponses();
return $micropubResponses->tokenHasNoScopeResponse();
}
$this->logMicropubRequest($request->all());
if (($request->input('h') === 'entry') || ($request->input('type.0') === 'h-entry')) {
if (stripos($tokenData->claims()->get('scope'), 'create') === false) {
$micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse();
}
$location = $this->hentryService->process($request->all(), $this->getCLientId());
// Return appropriate response based on the handler result
return response()->json([ return response()->json([
'response' => 'created', 'response' => $result['response'],
'location' => $location, 'location' => $result['url'] ?? null,
], 201)->header('Location', $location); ], 201)->header('Location', $result['url']);
} } catch (\InvalidArgumentException $e) {
if ($request->input('h') === 'card' || $request->input('type.0') === 'h-card') {
if (stripos($tokenData->claims()->get('scope'), 'create') === false) {
$micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse();
}
$location = $this->hcardService->process($request->all());
return response()->json([ return response()->json([
'response' => 'created', 'error' => 'invalid_request',
'location' => $location, 'error_description' => $e->getMessage(),
], 201)->header('Location', $location); ], 400);
} } catch (MicropubHandlerException) {
if ($request->input('action') === 'update') {
if (stripos($tokenData->claims()->get('scope'), 'update') === false) {
$micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse();
}
return $this->updateService->process($request->all());
}
return response()->json([ return response()->json([
'response' => 'error', 'error' => 'Unknown Micropub type',
'error_description' => 'unsupported_request_type', 'error_description' => 'The request could not be processed by this server',
], 500); ], 500);
} catch (InvalidTokenScopeException) {
return response()->json([
'error' => 'invalid_scope',
'error_description' => 'The token does not have the required scope for this request',
], 403);
} catch (\Exception) {
return response()->json([
'error' => 'server_error',
'error_description' => 'An error occurred processing the request',
], 500);
}
} }
/** /**
@ -120,12 +83,6 @@ class MicropubController extends Controller
*/ */
public function get(Request $request): JsonResponse public function get(Request $request): JsonResponse
{ {
try {
$tokenData = $this->tokenService->validateToken($request->input('access_token'));
} catch (RequiredConstraintsViolated|InvalidTokenStructure) {
return (new MicropubResponses())->invalidTokenResponse();
}
if ($request->input('q') === 'syndicate-to') { if ($request->input('q') === 'syndicate-to') {
return response()->json([ return response()->json([
'syndicate-to' => SyndicationTarget::all(), 'syndicate-to' => SyndicationTarget::all(),
@ -157,36 +114,17 @@ class MicropubController extends Controller
]); ]);
} }
// default response is just to return the token data // the default response is just to return the token data
/** @var Token $tokenData */
$tokenData = $request->input('token_data');
return response()->json([ return response()->json([
'response' => 'token', 'response' => 'token',
'token' => [ 'token' => [
'me' => $tokenData->claims()->get('me'), 'me' => $tokenData['me'],
'scope' => $tokenData->claims()->get('scope'), 'scope' => $tokenData['scope'],
'client_id' => $tokenData->claims()->get('client_id'), 'client_id' => $tokenData['client_id'],
], ],
]); ]);
} }
/**
* Determine the client id from the access token sent with the request.
*
* @throws RequiredConstraintsViolated
*/
private function getClientId(): string
{
return resolve(TokenService::class)
->validateToken(app('request')->input('access_token'))
->claims()->get('client_id');
}
/**
* Save the details of the micropub request to a log file.
*/
private function logMicropubRequest(array $request): void
{
$logger = new Logger('micropub');
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')));
$logger->debug('MicropubLog', $request);
}
} }

View file

@ -7,54 +7,29 @@ namespace App\Http\Controllers;
use App\Http\Responses\MicropubResponses; use App\Http\Responses\MicropubResponses;
use App\Jobs\ProcessMedia; use App\Jobs\ProcessMedia;
use App\Models\Media; use App\Models\Media;
use App\Services\TokenService;
use Exception; use Exception;
use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Http\File;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\ImageManager; use Intervention\Image\ImageManager;
use Lcobucci\JWT\Token\InvalidTokenStructure;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Uuid;
/**
* @psalm-suppress UnusedClass
*/
class MicropubMediaController extends Controller class MicropubMediaController extends Controller
{ {
protected TokenService $tokenService;
public function __construct(TokenService $tokenService)
{
$this->tokenService = $tokenService;
}
public function getHandler(Request $request): JsonResponse public function getHandler(Request $request): JsonResponse
{ {
try { $tokenData = $request->input('token_data');
$tokenData = $this->tokenService->validateToken($request->input('access_token'));
} catch (RequiredConstraintsViolated|InvalidTokenStructure) {
$micropubResponses = new MicropubResponses();
return $micropubResponses->invalidTokenResponse(); $scopes = $tokenData['scope'];
if (is_string($scopes)) {
$scopes = explode(' ', $scopes);
} }
if (! in_array('create', $scopes, true)) {
if ($tokenData->claims()->has('scope') === false) { return (new MicropubResponses)->insufficientScopeResponse();
$micropubResponses = new MicropubResponses();
return $micropubResponses->tokenHasNoScopeResponse();
}
if (Str::contains($tokenData->claims()->get('scope'), 'create') === false) {
$micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse();
} }
if ($request->input('q') === 'last') { if ($request->input('q') === 'last') {
@ -105,24 +80,14 @@ class MicropubMediaController extends Controller
*/ */
public function media(Request $request): JsonResponse public function media(Request $request): JsonResponse
{ {
try { $tokenData = $request->input('token_data');
$tokenData = $this->tokenService->validateToken($request->input('access_token'));
} catch (RequiredConstraintsViolated|InvalidTokenStructure) {
$micropubResponses = new MicropubResponses();
return $micropubResponses->invalidTokenResponse(); $scopes = $tokenData['scope'];
if (is_string($scopes)) {
$scopes = explode(' ', $scopes);
} }
if (! in_array('create', $scopes, true)) {
if ($tokenData->claims()->has('scope') === false) { return (new MicropubResponses)->insufficientScopeResponse();
$micropubResponses = new MicropubResponses();
return $micropubResponses->tokenHasNoScopeResponse();
}
if (Str::contains($tokenData->claims()->get('scope'), 'create') === false) {
$micropubResponses = new MicropubResponses();
return $micropubResponses->insufficientScopeResponse();
} }
if ($request->hasFile('file') === false) { if ($request->hasFile('file') === false) {
@ -133,7 +98,10 @@ class MicropubMediaController extends Controller
], 400); ], 400);
} }
if ($request->file('file')->isValid() === false) { /** @var UploadedFile $file */
$file = $request->file('file');
if ($file->isValid() === false) {
return response()->json([ return response()->json([
'response' => 'error', 'response' => 'error',
'error' => 'invalid_request', 'error' => 'invalid_request',
@ -141,7 +109,7 @@ class MicropubMediaController extends Controller
], 400); ], 400);
} }
$filename = $this->saveFile($request->file('file')); $filename = Storage::disk('local')->putFile('media', $file);
/** @var ImageManager $manager */ /** @var ImageManager $manager */
$manager = resolve(ImageManager::class); $manager = resolve(ImageManager::class);
@ -154,19 +122,12 @@ class MicropubMediaController extends Controller
} }
$media = Media::create([ $media = Media::create([
'token' => $request->bearerToken(), 'token' => $request->input('access_token'),
'path' => 'media/' . $filename, 'path' => $filename,
'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()), 'type' => $this->getFileTypeFromMimeType($request->file('file')->getMimeType()),
'image_widths' => $width, 'image_widths' => $width,
]); ]);
// put the file on S3 initially, the ProcessMedia job may edit this
Storage::disk('s3')->putFileAs(
'media',
new File(storage_path('app') . '/' . $filename),
$filename
);
ProcessMedia::dispatch($filename); ProcessMedia::dispatch($filename);
return response()->json([ return response()->json([
@ -188,7 +149,7 @@ class MicropubMediaController extends Controller
*/ */
private function getFileTypeFromMimeType(string $mimeType): string private function getFileTypeFromMimeType(string $mimeType): string
{ {
//try known images // try known images
$imageMimeTypes = [ $imageMimeTypes = [
'image/gif', 'image/gif',
'image/jpeg', 'image/jpeg',
@ -200,7 +161,7 @@ class MicropubMediaController extends Controller
if (in_array($mimeType, $imageMimeTypes)) { if (in_array($mimeType, $imageMimeTypes)) {
return 'image'; return 'image';
} }
//try known video // try known video
$videoMimeTypes = [ $videoMimeTypes = [
'video/mp4', 'video/mp4',
'video/mpeg', 'video/mpeg',
@ -211,7 +172,7 @@ class MicropubMediaController extends Controller
if (in_array($mimeType, $videoMimeTypes)) { if (in_array($mimeType, $videoMimeTypes)) {
return 'video'; return 'video';
} }
//try known audio types // try known audio types
$audioMimeTypes = [ $audioMimeTypes = [
'audio/midi', 'audio/midi',
'audio/mpeg', 'audio/mpeg',
@ -230,7 +191,7 @@ class MicropubMediaController extends Controller
* *
* @throws Exception * @throws Exception
*/ */
private function saveFile(UploadedFile $file): string private function saveFileToLocal(UploadedFile $file): string
{ {
$filename = Uuid::uuid4()->toString() . '.' . $file->extension(); $filename = Uuid::uuid4()->toString() . '.' . $file->extension();
Storage::disk('local')->putFileAs('', $file, $filename); Storage::disk('local')->putFileAs('', $file, $filename);

View file

@ -14,8 +14,6 @@ use Jonnybarnes\IndieWeb\Numbers;
/** /**
* @todo Need to sort out Twitter and webmentions! * @todo Need to sort out Twitter and webmentions!
*
* @psalm-suppress UnusedClass
*/ */
class NotesController extends Controller class NotesController extends Controller
{ {
@ -67,7 +65,7 @@ class NotesController extends Controller
*/ */
public function redirect(int $decId): RedirectResponse public function redirect(int $decId): RedirectResponse
{ {
return redirect(config('app.url') . '/notes/' . (new Numbers())->numto60($decId)); return redirect(config('app.url') . '/notes/' . (new Numbers)->numto60($decId));
} }
/** /**

View file

@ -7,9 +7,6 @@ namespace App\Http\Controllers;
use App\Models\Place; use App\Models\Place;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class PlacesController extends Controller class PlacesController extends Controller
{ {
/** /**

View file

@ -6,9 +6,6 @@ use App\Models\Note;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\View\View; use Illuminate\View\View;
/**
* @psalm-suppress UnusedClass
*/
class SearchController extends Controller class SearchController extends Controller
{ {
public function search(Request $request): View public function search(Request $request): View

View file

@ -1,55 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\RedirectResponse;
/**
* @psalm-suppress UnusedClass
*/
class ShortURLsController extends Controller
{
/*
|--------------------------------------------------------------------------
| Short URL Controller
|--------------------------------------------------------------------------
|
| This redirects the short urls to long ones
|
*/
/**
* Redirect from '/' to the long url.
*/
public function baseURL(): RedirectResponse
{
return redirect(config('app.url'));
}
/**
* Redirect from '/@' to a twitter profile.
*/
public function twitter(): RedirectResponse
{
return redirect('https://twitter.com/jonnybarnes');
}
/**
* Redirect a short url of this site out to a long one based on post type.
*
* Further redirects may happen.
*/
public function expandType(string $type, string $postId): RedirectResponse
{
if ($type === 't') {
$type = 'notes';
}
if ($type === 'b') {
$type = 'blog/s';
}
return redirect(config('app.url') . '/' . $type . '/' . $postId);
}
}

View file

@ -1,109 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Services\TokenService;
use GuzzleHttp\Client as GuzzleClient;
use GuzzleHttp\Exception\BadResponseException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use IndieAuth\Client;
use JsonException;
/**
* @psalm-suppress UnusedClass
*/
class TokenEndpointController extends Controller
{
/**
* @var Client The IndieAuth Client.
*/
protected Client $client;
/**
* @var GuzzleClient The GuzzleHttp client.
*/
protected GuzzleClient $guzzle;
protected TokenService $tokenService;
/**
* Inject the dependencies.
*/
public function __construct(
Client $client,
GuzzleClient $guzzle,
TokenService $tokenService
) {
$this->client = $client;
$this->guzzle = $guzzle;
$this->tokenService = $tokenService;
}
/**
* If the user has authd via the IndieAuth protocol, issue a valid token.
*/
public function create(Request $request): JsonResponse
{
$auth = $this->verifyIndieAuthCode(
config('url.authorization_endpoint'),
$request->input('code'),
$request->input('redirect_uri'),
$request->input('client_id'),
);
if ($auth === null || ! array_key_exists('me', $auth)) {
return response()->json([
'error' => 'There was an error verifying the IndieAuth code',
], 401);
}
$scope = $auth['scope'] ?? '';
$tokenData = [
'me' => config('app.url'),
'client_id' => $request->input('client_id'),
'scope' => $scope,
];
$token = $this->tokenService->getNewToken($tokenData);
$content = [
'me' => config('app.url'),
'scope' => $scope,
'access_token' => $token,
];
return response()->json($content);
}
protected function verifyIndieAuthCode(
string $authorizationEndpoint,
string $code,
string $redirectUri,
string $clientId
): ?array {
try {
$response = $this->guzzle->request('POST', $authorizationEndpoint, [
'headers' => [
'Accept' => 'application/json',
],
'form_params' => [
'code' => $code,
'me' => config('app.url'),
'redirect_uri' => $redirectUri,
'client_id' => $clientId,
],
]);
} catch (BadResponseException) {
return null;
}
try {
$authData = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
return null;
}
return $authData;
}
}

View file

@ -12,9 +12,6 @@ use Illuminate\Http\Response;
use Illuminate\View\View; use Illuminate\View\View;
use Jonnybarnes\IndieWeb\Numbers; use Jonnybarnes\IndieWeb\Numbers;
/**
* @psalm-suppress UnusedClass
*/
class WebMentionsController extends Controller class WebMentionsController extends Controller
{ {
/** /**
@ -33,7 +30,7 @@ class WebMentionsController extends Controller
*/ */
public function receive(Request $request): Response public function receive(Request $request): Response
{ {
//first we trivially reject requests that lack all required inputs // first we trivially reject requests that lack all required inputs
if (($request->has('target') !== true) || ($request->has('source') !== true)) { if (($request->has('target') !== true) || ($request->has('source') !== true)) {
return response( return response(
'You need both the target and source parameters', 'You need both the target and source parameters',
@ -41,12 +38,12 @@ class WebMentionsController extends Controller
); );
} }
//next check the $target is valid // next check the $target is valid
$path = parse_url($request->input('target'), PHP_URL_PATH); $path = parse_url($request->input('target'), PHP_URL_PATH);
$pathParts = explode('/', $path); $pathParts = explode('/', $path);
if ($pathParts[1] === 'notes') { if ($pathParts[1] === 'notes') {
//we have a note // we have a note
$noteId = $pathParts[2]; $noteId = $pathParts[2];
try { try {
$note = Note::findOrFail(resolve(Numbers::class)->b60tonum($noteId)); $note = Note::findOrFail(resolve(Numbers::class)->b60tonum($noteId));

View file

@ -1,74 +0,0 @@
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\LinkHeadersMiddleware::class,
\App\Http\Middleware\LocalhostSessionMiddleware::class,
\App\Http\Middleware\CSPHeader::class,
],
'api' => [
// \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class.':api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
/**
* The application's middleware aliases.
*
* Aliases may be used instead of class names to conveniently assign middleware to routes and groups.
*
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'micropub.token' => \App\Http\Middleware\VerifyMicropubToken::class,
'myauth' => \App\Http\Middleware\MyAuthMiddleware::class,
'cors' => \App\Http\Middleware\CorsHeaders::class,
];
}

View file

@ -1,48 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Symfony\Component\HttpFoundation\Response;
class CSPHeader
{
/**
* Handle an incoming request.
*
* @psalm-suppress PossiblyUnusedMethod
*/
public function handle(Request $request, Closure $next): Response
{
if (App::environment('local', 'development')) {
return $next($request);
}
// headers have to be single-line strings,
// so we concat multiple lines
// phpcs:disable Generic.Files.LineLength.TooLong
return $next($request)
->header(
'Content-Security-Policy',
"default-src 'self'; " .
"style-src 'self' 'unsafe-inline' cloud.typography.com jonnybarnes.uk; " .
"img-src 'self' data: blob: https://pbs.twimg.com https://jbuk-media.s3-eu-west-1.amazonaws.com https://jbuk-media-dev.s3-eu-west-1.amazonaws.com https://secure.gravatar.com https://graph.facebook.com *.fbcdn.net https://*.cdninstagram.com https://*.4sqi.net https://upload.wikimedia.org; " .
"font-src 'self' data:; " .
"frame-src 'self' https://www.youtube.com blob:; " .
'upgrade-insecure-requests; ' .
'block-all-mixed-content; ' .
'report-to csp-endpoint; ' .
'report-uri https://jonnybarnes.report-uri.io/r/default/csp/enforce;'
)->header(
'Report-To',
'{' .
"'url': 'https://jonnybarnes.report-uri.io/r/default/csp/enforce', " .
"'group': 'csp-endpoint', " .
"'max-age': 10886400" .
'}'
);
// phpcs:enable Generic.Files.LineLength.TooLong
}
}

View file

@ -10,8 +10,6 @@ class CorsHeaders
{ {
/** /**
* Handle an incoming request. * Handle an incoming request.
*
* @psalm-suppress PossiblyUnusedMethod
*/ */
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {

View file

@ -10,16 +10,15 @@ class LinkHeadersMiddleware
{ {
/** /**
* Handle an incoming request. * Handle an incoming request.
*
* @psalm-suppress PossiblyUnusedMethod
*/ */
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {
$response = $next($request); $response = $next($request);
$response->header('Link', '<https://indieauth.com/auth>; rel="authorization_endpoint"', false); $response->header('Link', '<' . route('indieauth.metadata') . '>; rel="indieauth-metadata"', false);
$response->header('Link', '<' . config('app.url') . '/api/token>; rel="token_endpoint"', false); $response->header('Link', '<' . route('indieauth.start') . '>; rel="authorization_endpoint"', false);
$response->header('Link', '<' . config('app.url') . '/api/post>; rel="micropub"', false); $response->header('Link', '<' . route('indieauth.token') . '>; rel="token_endpoint"', false);
$response->header('Link', '<' . config('app.url') . '/webmention>; rel="webmention"', false); $response->header('Link', '<' . route('micropub-endpoint') . '>; rel="micropub"', false);
$response->header('Link', '<' . route('webmention-endpoint') . '>; rel="webmention"', false);
return $response; return $response;
} }

View file

@ -14,8 +14,6 @@ class LocalhostSessionMiddleware
* Whilst we are developing locally, automatically log in as * Whilst we are developing locally, automatically log in as
* `['me' => config('app.url')]` as I cant manually log in as * `['me' => config('app.url')]` as I cant manually log in as
* a .localhost domain. * a .localhost domain.
*
* @psalm-suppress PossiblyUnusedMethod
*/ */
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
class LogMicropubRequest
{
public function handle(Request $request, Closure $next): Response|JsonResponse
{
$logger = new Logger('micropub');
$logger->pushHandler(new StreamHandler(storage_path('logs/micropub.log')));
$logger->debug('MicropubLog', $request->all());
return $next($request);
}
}

View file

@ -13,13 +13,13 @@ class MyAuthMiddleware
{ {
/** /**
* Check the user is logged in. * Check the user is logged in.
*
* @psalm-suppress PossiblyUnusedMethod
*/ */
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {
if (Auth::check() === false) { if (Auth::check() === false) {
// theyre not logged in, so send them to login form // theyre not logged in, so send them to login form
redirect()->setIntendedUrl($request->fullUrl());
return redirect()->route('login'); return redirect()->route('login');
} }

View file

@ -10,8 +10,6 @@ class ValidateSignature extends Middleware
* The names of the query string parameters that should be ignored. * The names of the query string parameters that should be ignored.
* *
* @var array<int, string> * @var array<int, string>
*
* @psalm-suppress PossiblyUnusedProperty
*/ */
protected $except = [ protected $except = [
// 'fbclid', // 'fbclid',

View file

@ -4,8 +4,14 @@ declare(strict_types=1);
namespace App\Http\Middleware; namespace App\Http\Middleware;
use App\Http\Responses\MicropubResponses;
use Closure; use Closure;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Encoding\CannotDecodeContent;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Token\InvalidTokenStructure;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
class VerifyMicropubToken class VerifyMicropubToken
@ -13,24 +19,63 @@ class VerifyMicropubToken
/** /**
* Handle an incoming request. * Handle an incoming request.
* *
* @psalm-suppress PossiblyUnusedMethod * @param Closure(Request): (Response) $next
*/ */
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {
$rawToken = null;
if ($request->input('access_token')) { if ($request->input('access_token')) {
return $next($request); $rawToken = $request->input('access_token');
} } elseif ($request->bearerToken()) {
$rawToken = $request->bearerToken();
if ($request->bearerToken()) {
return $next($request->merge([
'access_token' => $request->bearerToken(),
]));
} }
if (! $rawToken) {
return response()->json([ return response()->json([
'response' => 'error', 'response' => 'error',
'error' => 'unauthorized', 'error' => 'unauthorized',
'error_description' => 'No access token was provided in the request', 'error_description' => 'No access token was provided in the request',
], 401); ], 401);
} }
try {
$tokenData = $this->validateToken($rawToken);
} catch (RequiredConstraintsViolated|InvalidTokenStructure|CannotDecodeContent) {
$micropubResponses = new MicropubResponses;
return $micropubResponses->invalidTokenResponse();
}
if ($tokenData->claims()->has('scope') === false) {
$micropubResponses = new MicropubResponses;
return $micropubResponses->tokenHasNoScopeResponse();
}
return $next($request->merge([
'access_token' => $rawToken,
'token_data' => [
'me' => $tokenData->claims()->get('me'),
'scope' => $tokenData->claims()->get('scope'),
'client_id' => $tokenData->claims()->get('client_id'),
],
]));
}
/**
* Check the token signature is valid.
*/
private function validateToken(string $bearerToken): Token
{
$config = resolve(Configuration::class);
$token = $config->parser()->parse($bearerToken);
$constraints = $config->validationConstraints();
$config->validator()->assert($token, ...$constraints);
return $token;
}
} }

View file

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Arr;
class MicropubRequest extends FormRequest
{
protected array $micropubData = [];
public function rules(): array
{
return [
// Validation rules
];
}
public function getMicropubData(): array
{
return $this->micropubData;
}
public function getType(): ?string
{
// Return consistent type regardless of input format
return $this->micropubData['type'] ?? null;
}
protected function prepareForValidation(): void
{
// Normalize the request data based on content type
if ($this->isJson()) {
$this->normalizeMicropubJson();
} else {
$this->normalizeMicropubForm();
}
}
private function normalizeMicropubJson(): void
{
$json = $this->json();
if ($json === null) {
throw new \InvalidArgumentException('`isJson()` passed but there is no json data');
}
$data = $json->all();
// Convert JSON type (h-entry) to simple type (entry)
if (isset($data['type']) && is_array($data['type'])) {
$type = current($data['type']);
if (strpos($type, 'h-') === 0) {
$this->micropubData['type'] = substr($type, 2);
}
}
// Or set the type to update
elseif (isset($data['action']) && $data['action'] === 'update') {
$this->micropubData['type'] = 'update';
}
// Add in the token data
$this->micropubData['token_data'] = $data['token_data'];
// Add h-entry values
$this->micropubData['content'] = Arr::get($data, 'properties.content.0');
$this->micropubData['in-reply-to'] = Arr::get($data, 'properties.in-reply-to.0');
$this->micropubData['published'] = Arr::get($data, 'properties.published.0');
$this->micropubData['location'] = Arr::get($data, 'location');
$this->micropubData['bookmark-of'] = Arr::get($data, 'properties.bookmark-of.0');
$this->micropubData['like-of'] = Arr::get($data, 'properties.like-of.0');
$this->micropubData['mp-syndicate-to'] = Arr::get($data, 'properties.mp-syndicate-to');
// Add h-card values
$this->micropubData['name'] = Arr::get($data, 'properties.name.0');
$this->micropubData['description'] = Arr::get($data, 'properties.description.0');
$this->micropubData['geo'] = Arr::get($data, 'properties.geo.0');
// Add checkin value
$this->micropubData['checkin'] = Arr::get($data, 'checkin');
$this->micropubData['syndication'] = Arr::get($data, 'properties.syndication.0');
}
private function normalizeMicropubForm(): void
{
// Convert form h=entry to type=entry
if ($h = $this->input('h')) {
$this->micropubData['type'] = $h;
}
// Add some fields to the micropub data with default null values
$this->micropubData['in-reply-to'] = null;
$this->micropubData['published'] = null;
$this->micropubData['location'] = null;
$this->micropubData['description'] = null;
$this->micropubData['geo'] = null;
$this->micropubData['latitude'] = null;
$this->micropubData['longitude'] = null;
// Map form fields to micropub data
foreach ($this->except(['h', 'access_token']) as $key => $value) {
$this->micropubData[$key] = $value;
}
}
}

View file

@ -24,8 +24,7 @@ class DownloadWebMention implements ShouldQueue
*/ */
public function __construct( public function __construct(
protected string $source protected string $source
) { ) {}
}
/** /**
* Execute the job. * Execute the job.
@ -36,30 +35,30 @@ class DownloadWebMention implements ShouldQueue
public function handle(Client $guzzle): void public function handle(Client $guzzle): void
{ {
$response = $guzzle->request('GET', $this->source); $response = $guzzle->request('GET', $this->source);
//4XX and 5XX responses should get Guzzle to throw an exception, // 4XX and 5XX responses should get Guzzle to throw an exception,
//Laravel should catch and retry these automatically. // Laravel should catch and retry these automatically.
if ($response->getStatusCode() === 200) { if ($response->getStatusCode() === 200) {
$filesystem = new FileSystem(); $filesystem = new FileSystem;
$filename = storage_path('HTML') . '/' . $this->createFilenameFromURL($this->source); $filename = storage_path('HTML') . '/' . $this->createFilenameFromURL($this->source);
//backup file first // backup file first
$filenameBackup = $filename . '.' . date('Y-m-d') . '.backup'; $filenameBackup = $filename . '.' . date('Y-m-d') . '.backup';
if ($filesystem->exists($filename)) { if ($filesystem->exists($filename)) {
$filesystem->copy($filename, $filenameBackup); $filesystem->copy($filename, $filenameBackup);
} }
//check if base directory exists // check if base directory exists
if (! $filesystem->exists($filesystem->dirname($filename))) { if (! $filesystem->exists($filesystem->dirname($filename))) {
$filesystem->makeDirectory( $filesystem->makeDirectory(
$filesystem->dirname($filename), $filesystem->dirname($filename),
0755, //mode 0755, // mode
true //recursive true // recursive
); );
} }
//save new HTML // save new HTML
$filesystem->put( $filesystem->put(
$filename, $filename,
(string) $response->getBody() (string) $response->getBody()
); );
//remove backup if the same // remove backup if the same
if ($filesystem->exists($filenameBackup)) { if ($filesystem->exists($filenameBackup)) {
if ($filesystem->get($filename) === $filesystem->get($filenameBackup)) { if ($filesystem->get($filename) === $filesystem->get($filenameBackup)) {
$filesystem->delete($filenameBackup); $filesystem->delete($filenameBackup);

View file

@ -25,8 +25,7 @@ class ProcessBookmark implements ShouldQueue
*/ */
public function __construct( public function __construct(
protected Bookmark $bookmark protected Bookmark $bookmark
) { ) {}
}
/** /**
* Execute the job. * Execute the job.

View file

@ -30,8 +30,7 @@ class ProcessLike implements ShouldQueue
*/ */
public function __construct( public function __construct(
protected Like $like protected Like $like
) { ) {}
}
/** /**
* Execute the job. * Execute the job.
@ -50,7 +49,7 @@ class ProcessLike implements ShouldQueue
$this->like->content = $tweet->html; $this->like->content = $tweet->html;
$this->like->save(); $this->like->save();
//POSSE like // POSSE like
try { try {
$client->request( $client->request(
'POST', 'POST',

View file

@ -25,43 +25,45 @@ class ProcessMedia implements ShouldQueue
*/ */
public function __construct( public function __construct(
protected string $filename protected string $filename
) { ) {}
}
/** /**
* Execute the job. * Execute the job.
*/ */
public function handle(ImageManager $manager): void public function handle(ImageManager $manager): void
{ {
//open file // Load file
$file = Storage::disk('local')->get('media/' . $this->filename);
// Open file
try { try {
$image = $manager->read(storage_path('app') . '/' . $this->filename); $image = $manager->read($file);
} catch (DecoderException) { } catch (DecoderException) {
// not an image; delete file and end job // not an image; delete file and end job
unlink(storage_path('app') . '/' . $this->filename); Storage::disk('local')->delete('media/' . $this->filename);
return; return;
} }
//create smaller versions if necessary
// Save the file publicly
Storage::disk('public')->put('media/' . $this->filename, $file);
// Create smaller versions if necessary
if ($image->width() > 1000) { if ($image->width() > 1000) {
$filenameParts = explode('.', $this->filename); $filenameParts = explode('.', $this->filename);
$extension = array_pop($filenameParts); $extension = array_pop($filenameParts);
// the following achieves this data flow // the following achieves this data flow
// foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar // foo.bar.png => ['foo', 'bar', 'png'] => ['foo', 'bar'] => foo.bar
$basename = ltrim(array_reduce($filenameParts, function ($carry, $item) { $basename = trim(implode('.', $filenameParts), '.');
return $carry . '.' . $item;
}, ''), '.'); $medium = $image->resize(width: 1000);
$medium = $image->resize(1000, null, function ($constraint) { Storage::disk('public')->put('media/' . $basename . '-medium.' . $extension, (string) $medium->encode());
$constraint->aspectRatio();
}); $small = $image->resize(width: 500);
Storage::disk('s3')->put('media/' . $basename . '-medium.' . $extension, (string) $medium->encode()); Storage::disk('public')->put('media/' . $basename . '-small.' . $extension, (string) $small->encode());
$small = $image->resize(500, null, function ($constraint) {
$constraint->aspectRatio();
});
Storage::disk('s3')->put('media/' . $basename . '-small.' . $extension, (string) $small->encode());
} }
// now we can delete the locally saved image // Now we can delete the locally saved image
unlink(storage_path('app') . '/' . $this->filename); Storage::disk('local')->delete('media/' . $this->filename);
} }
} }

View file

@ -30,8 +30,7 @@ class ProcessWebMention implements ShouldQueue
public function __construct( public function __construct(
protected Note $note, protected Note $note,
protected string $source protected string $source
) { ) {}
}
/** /**
* Execute the job. * Execute the job.
@ -45,7 +44,7 @@ class ProcessWebMention implements ShouldQueue
try { try {
$response = $guzzle->request('GET', $this->source); $response = $guzzle->request('GET', $this->source);
} catch (RequestException $e) { } catch (RequestException $e) {
throw new RemoteContentNotFoundException(); throw new RemoteContentNotFoundException;
} }
$this->saveRemoteContent((string) $response->getBody(), $this->source); $this->saveRemoteContent((string) $response->getBody(), $this->source);
$microformats = Mf2\parse((string) $response->getBody(), $this->source); $microformats = Mf2\parse((string) $response->getBody(), $this->source);
@ -54,7 +53,7 @@ class ProcessWebMention implements ShouldQueue
// check webmention still references target // check webmention still references target
// we try each type of mention (reply/like/repost) // we try each type of mention (reply/like/repost)
if ($webmention->type === 'in-reply-to') { if ($webmention->type === 'in-reply-to') {
if ($parser->checkInReplyTo($microformats, $this->note->longurl) === false) { if ($parser->checkInReplyTo($microformats, $this->note->uri) === false) {
// it doesnt so delete // it doesnt so delete
$webmention->delete(); $webmention->delete();
@ -68,7 +67,7 @@ class ProcessWebMention implements ShouldQueue
return; return;
} }
if ($webmention->type === 'like-of') { if ($webmention->type === 'like-of') {
if ($parser->checkLikeOf($microformats, $this->note->longurl) === false) { if ($parser->checkLikeOf($microformats, $this->note->uri) === false) {
// it doesnt so delete // it doesnt so delete
$webmention->delete(); $webmention->delete();
@ -76,7 +75,7 @@ class ProcessWebMention implements ShouldQueue
} // note we dont need to do anything if it still is a like } // note we dont need to do anything if it still is a like
} }
if ($webmention->type === 'repost-of') { if ($webmention->type === 'repost-of') {
if ($parser->checkRepostOf($microformats, $this->note->longurl) === false) { if ($parser->checkRepostOf($microformats, $this->note->uri) === false) {
// it doesnt so delete // it doesnt so delete
$webmention->delete(); $webmention->delete();
@ -86,13 +85,13 @@ class ProcessWebMention implements ShouldQueue
}// foreach }// foreach
// no webmention in the db so create new one // no webmention in the db so create new one
$webmention = new WebMention(); $webmention = new WebMention;
$type = $parser->getMentionType($microformats); // throw error here? $type = $parser->getMentionType($microformats); // throw error here?
dispatch(new SaveProfileImage($microformats)); dispatch(new SaveProfileImage($microformats));
$webmention->source = $this->source; $webmention->source = $this->source;
$webmention->target = $this->note->longurl; $webmention->target = $this->note->uri;
$webmention->commentable_id = $this->note->id; $webmention->commentable_id = $this->note->id;
$webmention->commentable_type = 'App\Model\Note'; $webmention->commentable_type = Note::class;
$webmention->type = $type; $webmention->type = $type;
$webmention->mf2 = json_encode($microformats); $webmention->mf2 = json_encode($microformats);
$webmention->save(); $webmention->save();

View file

@ -25,8 +25,7 @@ class SaveProfileImage implements ShouldQueue
*/ */
public function __construct( public function __construct(
protected array $microformats protected array $microformats
) { ) {}
}
/** /**
* Execute the job. * Execute the job.
@ -50,7 +49,7 @@ class SaveProfileImage implements ShouldQueue
$home = array_shift($home); $home = array_shift($home);
} }
//dont save pbs.twimg.com links // dont save pbs.twimg.com links
if ( if (
$photo $photo
&& parse_url($photo, PHP_URL_HOST) !== 'pbs.twimg.com' && parse_url($photo, PHP_URL_HOST) !== 'pbs.twimg.com'

View file

@ -23,8 +23,7 @@ class SaveScreenshot implements ShouldQueue
*/ */
public function __construct( public function __construct(
protected Bookmark $bookmark protected Bookmark $bookmark
) { ) {}
}
/** /**
* Execute the job. * Execute the job.

View file

@ -27,8 +27,7 @@ class SendWebMentions implements ShouldQueue
*/ */
public function __construct( public function __construct(
protected Note $note protected Note $note
) { ) {}
}
/** /**
* Execute the job. * Execute the job.
@ -46,7 +45,7 @@ class SendWebMentions implements ShouldQueue
$guzzle = resolve(Client::class); $guzzle = resolve(Client::class);
$guzzle->post($endpoint, [ $guzzle->post($endpoint, [
'form_params' => [ 'form_params' => [
'source' => $this->note->longurl, 'source' => $this->note->uri,
'target' => $url, 'target' => $url,
], ],
]); ]);
@ -62,7 +61,7 @@ class SendWebMentions implements ShouldQueue
public function discoverWebmentionEndpoint(string $url): ?string public function discoverWebmentionEndpoint(string $url): ?string
{ {
// lets not send webmentions to myself // lets not send webmentions to myself
if (parse_url($url, PHP_URL_HOST) === config('url.longurl')) { if (parse_url($url, PHP_URL_HOST) === parse_url(config('app.url'), PHP_URL_HOST)) {
return null; return null;
} }
if (Str::startsWith($url, '/notes/tagged/')) { if (Str::startsWith($url, '/notes/tagged/')) {
@ -73,7 +72,7 @@ class SendWebMentions implements ShouldQueue
$guzzle = resolve(Client::class); $guzzle = resolve(Client::class);
$response = $guzzle->get($url); $response = $guzzle->get($url);
//check HTTP Headers for webmention endpoint // check HTTP Headers for webmention endpoint
$links = Header::parse($response->getHeader('Link')); $links = Header::parse($response->getHeader('Link'));
foreach ($links as $link) { foreach ($links as $link) {
if (array_key_exists('rel', $link) && mb_stristr($link['rel'], 'webmention')) { if (array_key_exists('rel', $link) && mb_stristr($link['rel'], 'webmention')) {
@ -81,7 +80,7 @@ class SendWebMentions implements ShouldQueue
} }
} }
//failed to find a header so parse HTML // failed to find a header so parse HTML
$html = (string) $response->getBody(); $html = (string) $response->getBody();
$mf2 = new \Mf2\Parser($html, $url); $mf2 = new \Mf2\Parser($html, $url);
@ -109,7 +108,7 @@ class SendWebMentions implements ShouldQueue
} }
$urls = []; $urls = [];
$dom = new \DOMDocument(); $dom = new \DOMDocument;
$dom->loadHTML($html); $dom->loadHTML($html);
$anchors = $dom->getElementsByTagName('a'); $anchors = $dom->getElementsByTagName('a');
foreach ($anchors as $anchor) { foreach ($anchors as $anchor) {

View file

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Jobs;
use App\Models\Note;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class SyndicateNoteToBluesky implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(
protected Note $note
) {}
/**
* Execute the job.
*
* @throws GuzzleException
*/
public function handle(Client $guzzle): void
{
// We can only make the request if we have an access token
if (config('bridgy.bluesky_token') === null) {
return;
}
// Make micropub request
$response = $guzzle->request(
'POST',
'https://brid.gy/micropub',
[
'headers' => [
'Authorization' => 'Bearer ' . config('bridgy.bluesky_token'),
],
'json' => [
'type' => ['h-entry'],
'properties' => [
'content' => [$this->note->getRawOriginal('note')],
],
],
]
);
// Parse for syndication URL
if ($response->getStatusCode() === 201) {
$this->note->bluesky_url = $response->getHeader('Location')[0];
$this->note->save();
}
}
}

View file

@ -22,8 +22,7 @@ class SyndicateNoteToMastodon implements ShouldQueue
*/ */
public function __construct( public function __construct(
protected Note $note protected Note $note
) { ) {}
}
/** /**
* Execute the job. * Execute the job.

View file

@ -58,10 +58,10 @@ class Article extends Model
{ {
return Attribute::get( return Attribute::get(
get: function () { get: function () {
$environment = new Environment(); $environment = new Environment;
$environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new CommonMarkCoreExtension);
$environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); $environment->addRenderer(FencedCode::class, new FencedCodeRenderer);
$environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer);
$markdownConverter = new MarkdownConverter($environment); $markdownConverter = new MarkdownConverter($environment);
return $markdownConverter->convert($this->main)->getContent(); return $markdownConverter->convert($this->main)->getContent();

View file

@ -26,7 +26,7 @@ class Bookmark extends Model
return $this->belongsToMany('App\Models\Tag'); return $this->belongsToMany('App\Models\Tag');
} }
protected function longurl(): Attribute protected function local_uri(): Attribute
{ {
return Attribute::get( return Attribute::get(
get: fn () => config('app.url') . '/bookmarks/' . $this->id, get: fn () => config('app.url') . '/bookmarks/' . $this->id,

View file

@ -33,7 +33,7 @@ class Media extends Model
return $attributes['path']; return $attributes['path'];
} }
return config('filesystems.disks.s3.url') . '/' . $attributes['path']; return config('app.url') . '/storage/' . $attributes['path'];
} }
); );
} }
@ -78,7 +78,7 @@ class Media extends Model
$basename = $this->getBasename($path); $basename = $this->getBasename($path);
$extension = $this->getExtension($path); $extension = $this->getExtension($path);
return config('filesystems.disks.s3.url') . '/' . $basename . '-' . $size . '.' . $extension; return config('app.url') . '/storage/' . $basename . '-' . $size . '.' . $extension;
} }
private function getBasename(string $path): string private function getBasename(string $path): string

View file

@ -111,7 +111,7 @@ class Note extends Model
{ {
if ($value !== null) { if ($value !== null) {
$normalized = normalizer_normalize($value, Normalizer::FORM_C); $normalized = normalizer_normalize($value, Normalizer::FORM_C);
if ($normalized === '') { //we dont want to save empty strings to the db if ($normalized === '') { // we dont want to save empty strings to the db
$normalized = null; $normalized = null;
} }
$this->attributes['note'] = $normalized; $this->attributes['note'] = $normalized;
@ -124,7 +124,7 @@ class Note extends Model
public function getNoteAttribute(?string $value): ?string public function getNoteAttribute(?string $value): ?string
{ {
if ($value === null && $this->place !== null) { if ($value === null && $this->place !== null) {
$value = '📍: <a href="' . $this->place->longurl . '">' . $this->place->name . '</a>'; $value = '📍: <a href="' . $this->place->uri . '">' . $this->place->name . '</a>';
} }
// if $value is still null, just return null // if $value is still null, just return null
@ -172,16 +172,11 @@ class Note extends Model
return (string) resolve(Numbers::class)->numto60($this->id); return (string) resolve(Numbers::class)->numto60($this->id);
} }
public function getLongurlAttribute(): string public function getUriAttribute(): string
{ {
return config('app.url') . '/notes/' . $this->nb60id; return config('app.url') . '/notes/' . $this->nb60id;
} }
public function getShorturlAttribute(): string
{
return config('url.shorturl') . '/notes/' . $this->nb60id;
}
public function getIso8601Attribute(): string public function getIso8601Attribute(): string
{ {
return $this->updated_at->toISO8601String(); return $this->updated_at->toISO8601String();
@ -271,7 +266,7 @@ class Note extends Model
]); ]);
if ($oEmbed->httpstatus >= 400) { if ($oEmbed->httpstatus >= 400) {
throw new Exception(); throw new Exception;
} }
} catch (Exception $e) { } catch (Exception $e) {
return null; return null;
@ -388,18 +383,18 @@ class Note extends Model
'mentions_handle' => [ 'mentions_handle' => [
'prefix' => '@', 'prefix' => '@',
'pattern' => '([\w@.])+(\b)', 'pattern' => '([\w@.])+(\b)',
'generator' => new MentionGenerator(), 'generator' => new MentionGenerator,
], ],
], ],
]; ];
$environment = new Environment($config); $environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new CommonMarkCoreExtension);
$environment->addExtension(new AutolinkExtension()); $environment->addExtension(new AutolinkExtension);
$environment->addExtension(new MentionExtension()); $environment->addExtension(new MentionExtension);
$environment->addRenderer(Mention::class, new MentionRenderer()); $environment->addRenderer(Mention::class, new MentionRenderer);
$environment->addRenderer(FencedCode::class, new FencedCodeRenderer()); $environment->addRenderer(FencedCode::class, new FencedCodeRenderer);
$environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer()); $environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer);
$markdownConverter = new MarkdownConverter($environment); $markdownConverter = new MarkdownConverter($environment);
return $markdownConverter->convert($note)->getContent(); return $markdownConverter->convert($note)->getContent();

View file

@ -59,7 +59,7 @@ class Place extends Model
* sin(radians(places.latitude))))"; * sin(radians(places.latitude))))";
return $query return $query
->select() //pick the columns you want here. ->select() // pick the columns you want here.
->selectRaw("{$haversine} AS distance") ->selectRaw("{$haversine} AS distance")
->whereRaw("{$haversine} < ?", [$distance]); ->whereRaw("{$haversine} < ?", [$distance]);
} }
@ -74,24 +74,10 @@ class Place extends Model
])); ]));
} }
protected function longurl(): Attribute
{
return Attribute::get(
get: fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'],
);
}
protected function shorturl(): Attribute
{
return Attribute::get(
get: fn ($value, $attributes) => config('url.shorturl') . '/places/' . $attributes['slug'],
);
}
protected function uri(): Attribute protected function uri(): Attribute
{ {
return Attribute::get( return Attribute::get(
get: fn () => $this->longurl, get: static fn ($value, $attributes) => config('app.url') . '/places/' . $attributes['slug'],
); );
} }

View file

@ -42,7 +42,7 @@ class WebMention extends Model
return null; return null;
} }
$authorship = new Authorship(); $authorship = new Authorship;
$hCard = $authorship->findAuthor(json_decode($attributes['mf2'], true)); $hCard = $authorship->findAuthor(json_decode($attributes['mf2'], true));
if ($hCard === false) { if ($hCard === false) {
@ -109,13 +109,21 @@ class WebMention extends Model
/** /**
* Create the photo link. * Create the photo link.
*/ */
public function createPhotoLink(string $url): string public function createPhotoLink(string|array $url): string
{ {
if (is_array($url)) {
if (! array_key_exists('value', $url)) {
return '';
}
$url = $url['value'];
}
$url = normalize_url($url); $url = normalize_url($url);
$host = parse_url($url, PHP_URL_HOST); $host = parse_url($url, PHP_URL_HOST);
if ($host === 'pbs.twimg.com') { if ($host === 'pbs.twimg.com') {
//make sure we use HTTPS, we know twitter supports it // make sure we use HTTPS, we know twitter supports it
return str_replace('http://', 'https://', $url); return str_replace('http://', 'https://', $url);
} }
@ -127,12 +135,12 @@ class WebMention extends Model
$codebird = resolve(Codebird::class); $codebird = resolve(Codebird::class);
$info = $codebird->users_show(['screen_name' => $username]); $info = $codebird->users_show(['screen_name' => $username]);
$profile_image = $info->profile_image_url_https; $profile_image = $info->profile_image_url_https;
Cache::put($url, $profile_image, 10080); //1 week Cache::put($url, $profile_image, 10080); // 1 week
return $profile_image; return $profile_image;
} }
$filesystem = new Filesystem(); $filesystem = new Filesystem;
if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) { if ($filesystem->exists(public_path() . '/assets/profile-images/' . $host . '/image')) {
return '/assets/profile-images/' . $host . '/image'; return '/assets/profile-images/' . $host . '/image';
} }

View file

@ -9,15 +9,10 @@ use App\Models\Tag;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/**
* @todo Do we need psalm-suppress for these observer methods?
*/
class NoteObserver class NoteObserver
{ {
/** /**
* Listen to the Note created event. * Listen to the Note created event.=
*
* @psalm-suppress PossiblyUnusedMethod
*/ */
public function created(Note $note): void public function created(Note $note): void
{ {
@ -39,9 +34,7 @@ class NoteObserver
} }
/** /**
* Listen to the Note updated event. * Listen to the Note updated event.=
*
* @psalm-suppress PossiblyUnusedMethod
*/ */
public function updated(Note $note): void public function updated(Note $note): void
{ {
@ -65,9 +58,7 @@ class NoteObserver
} }
/** /**
* Listen to the Note deleting event. * Listen to the Note deleting event.=
*
* @psalm-suppress PossiblyUnusedMethod
*/ */
public function deleting(Note $note): void public function deleting(Note $note): void
{ {

View file

@ -88,9 +88,9 @@ class AppServiceProvider extends ServiceProvider
$this->app->bind('Lcobucci\JWT\Configuration', function () { $this->app->bind('Lcobucci\JWT\Configuration', function () {
$key = InMemory::plainText(config('app.key')); $key = InMemory::plainText(config('app.key'));
$config = Configuration::forSymmetricSigner(new Sha256(), $key); $config = Configuration::forSymmetricSigner(new Sha256, $key);
$config->setValidationConstraints(new SignedWith(new Sha256(), $key)); $config->setValidationConstraints(new SignedWith(new Sha256, $key));
return $config; return $config;
}); });
@ -98,7 +98,7 @@ class AppServiceProvider extends ServiceProvider
// Configure HtmlSanitizer // Configure HtmlSanitizer
$this->app->bind(HtmlSanitizer::class, function () { $this->app->bind(HtmlSanitizer::class, function () {
return new HtmlSanitizer( return new HtmlSanitizer(
(new HtmlSanitizerConfig()) (new HtmlSanitizerConfig)
->allowSafeElements() ->allowSafeElements()
->forceAttribute('a', 'rel', 'noopener nofollow') ->forceAttribute('a', 'rel', 'noopener nofollow')
); );

View file

@ -5,9 +5,6 @@ namespace App\Providers;
use Illuminate\Support\Facades\Gate; use Illuminate\Support\Facades\Gate;
use Laravel\Horizon\HorizonApplicationServiceProvider; use Laravel\Horizon\HorizonApplicationServiceProvider;
/**
* @psalm-suppress UnusedClass
*/
class HorizonServiceProvider extends HorizonApplicationServiceProvider class HorizonServiceProvider extends HorizonApplicationServiceProvider
{ {
/** /**

View file

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Providers;
use App\Services\Micropub\CardHandler;
use App\Services\Micropub\EntryHandler;
use App\Services\Micropub\MicropubHandlerRegistry;
use Illuminate\Support\ServiceProvider;
class MicropubServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(MicropubHandlerRegistry::class, function () {
$registry = new MicropubHandlerRegistry;
// Register handlers
$registry->register('card', new CardHandler);
$registry->register('entry', new EntryHandler);
return $registry;
});
}
}

View file

@ -6,13 +6,13 @@ namespace App\Services;
use App\Models\Article; use App\Models\Article;
class ArticleService extends Service class ArticleService
{ {
public function create(array $request, ?string $client = null): Article public function create(array $data): Article
{ {
return Article::create([ return Article::create([
'title' => $this->getDataByKey($request, 'name'), 'title' => $data['name'],
'main' => $this->getDataByKey($request, 'content'), 'main' => $data['content'],
'published' => true, 'published' => true,
]); ]);
} }

View file

@ -10,28 +10,29 @@ use App\Models\Bookmark;
use App\Models\Tag; use App\Models\Tag;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class BookmarkService extends Service class BookmarkService
{ {
/** /**
* Create a new Bookmark. * Create a new Bookmark.
*/ */
public function create(array $request, ?string $client = null): Bookmark public function create(array $data): Bookmark
{ {
if (Arr::get($request, 'properties.bookmark-of.0')) { if (Arr::get($data, 'properties.bookmark-of.0')) {
//micropub request // micropub request
$url = normalize_url(Arr::get($request, 'properties.bookmark-of.0')); $url = normalize_url(Arr::get($data, 'properties.bookmark-of.0'));
$name = Arr::get($request, 'properties.name.0'); $name = Arr::get($data, 'properties.name.0');
$content = Arr::get($request, 'properties.content.0'); $content = Arr::get($data, 'properties.content.0');
$categories = Arr::get($request, 'properties.category'); $categories = Arr::get($data, 'properties.category');
} }
if (Arr::get($request, 'bookmark-of')) { if (Arr::get($data, 'bookmark-of')) {
$url = normalize_url(Arr::get($request, 'bookmark-of')); $url = normalize_url(Arr::get($data, 'bookmark-of'));
$name = Arr::get($request, 'name'); $name = Arr::get($data, 'name');
$content = Arr::get($request, 'content'); $content = Arr::get($data, 'content');
$categories = Arr::get($request, 'category'); $categories = Arr::get($data, 'category');
} }
$bookmark = Bookmark::create([ $bookmark = Bookmark::create([
@ -54,6 +55,7 @@ class BookmarkService extends Service
* Given a URL, attempt to save it to the Internet Archive. * Given a URL, attempt to save it to the Internet Archive.
* *
* @throws InternetArchiveException * @throws InternetArchiveException
* @throws GuzzleException
*/ */
public function getArchiveLink(string $url): string public function getArchiveLink(string $url): string
{ {
@ -61,8 +63,8 @@ class BookmarkService extends Service
try { try {
$response = $client->request('GET', 'https://web.archive.org/save/' . $url); $response = $client->request('GET', 'https://web.archive.org/save/' . $url);
} catch (ClientException $e) { } catch (ClientException $e) {
//throw an exception to be caught // throw an exception to be caught
throw new InternetArchiveException(); throw new InternetArchiveException;
} }
if ($response->hasHeader('Content-Location')) { if ($response->hasHeader('Content-Location')) {
if (Str::startsWith(Arr::get($response->getHeader('Content-Location'), 0), '/web')) { if (Str::startsWith(Arr::get($response->getHeader('Content-Location'), 0), '/web')) {
@ -70,7 +72,7 @@ class BookmarkService extends Service
} }
} }
//throw an exception to be caught // throw an exception to be caught
throw new InternetArchiveException(); throw new InternetArchiveException;
} }
} }

View file

@ -8,19 +8,19 @@ use App\Jobs\ProcessLike;
use App\Models\Like; use App\Models\Like;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
class LikeService extends Service class LikeService
{ {
/** /**
* Create a new Like. * Create a new Like.
*/ */
public function create(array $request, ?string $client = null): Like public function create(array $data): Like
{ {
if (Arr::get($request, 'properties.like-of.0')) { if (Arr::get($data, 'properties.like-of.0')) {
//micropub request // micropub request
$url = normalize_url(Arr::get($request, 'properties.like-of.0')); $url = normalize_url(Arr::get($data, 'properties.like-of.0'));
} }
if (Arr::get($request, 'like-of')) { if (Arr::get($data, 'like-of')) {
$url = normalize_url(Arr::get($request, 'like-of')); $url = normalize_url(Arr::get($data, 'like-of'));
} }
$like = Like::create(['url' => $url]); $like = Like::create(['url' => $url]);

View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Services\Micropub;
use App\Exceptions\InvalidTokenScopeException;
use App\Services\PlaceService;
class CardHandler implements MicropubHandlerInterface
{
/**
* @throws InvalidTokenScopeException
*/
public function handle(array $data): array
{
// Handle h-card requests
$scopes = $data['token_data']['scope'];
if (is_string($scopes)) {
$scopes = explode(' ', $scopes);
}
if (! in_array('create', $scopes, true)) {
throw new InvalidTokenScopeException;
}
$location = resolve(PlaceService::class)->createPlace($data)->uri;
return [
'response' => 'created',
'url' => $location,
];
}
}

View file

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\Services\Micropub;
use App\Exceptions\InvalidTokenScopeException;
use App\Services\ArticleService;
use App\Services\BookmarkService;
use App\Services\LikeService;
use App\Services\NoteService;
class EntryHandler implements MicropubHandlerInterface
{
/**
* @throws InvalidTokenScopeException
*/
public function handle(array $data)
{
$scopes = $data['token_data']['scope'];
if (is_string($scopes)) {
$scopes = explode(' ', $scopes);
}
if (! in_array('create', $scopes, true)) {
throw new InvalidTokenScopeException;
}
$location = match (true) {
isset($data['like-of']) => resolve(LikeService::class)->create($data)->url,
isset($data['bookmark-of']) => resolve(BookmarkService::class)->create($data)->uri,
isset($data['name']) => resolve(ArticleService::class)->create($data)->link,
default => resolve(NoteService::class)->create($data)->uri,
};
return [
'response' => 'created',
'url' => $location,
];
}
}

View file

@ -1,32 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Services\Micropub;
use App\Services\PlaceService;
use Illuminate\Support\Arr;
class HCardService
{
/**
* Create a Place from h-card data, return the URL.
*/
public function process(array $request): string
{
$data = [];
if (Arr::get($request, 'properties.name')) {
$data['name'] = Arr::get($request, 'properties.name');
$data['description'] = Arr::get($request, 'properties.description');
$data['geo'] = Arr::get($request, 'properties.geo');
} else {
$data['name'] = Arr::get($request, 'name');
$data['description'] = Arr::get($request, 'description');
$data['geo'] = Arr::get($request, 'geo');
$data['latitude'] = Arr::get($request, 'latitude');
$data['longitude'] = Arr::get($request, 'longitude');
}
return resolve(PlaceService::class)->createPlace($data)->longurl;
}
}

View file

@ -1,34 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Services\Micropub;
use App\Services\ArticleService;
use App\Services\BookmarkService;
use App\Services\LikeService;
use App\Services\NoteService;
use Illuminate\Support\Arr;
class HEntryService
{
/**
* Create the relevant model from some h-entry data.
*/
public function process(array $request, ?string $client = null): ?string
{
if (Arr::get($request, 'properties.like-of') || Arr::get($request, 'like-of')) {
return resolve(LikeService::class)->create($request)->longurl;
}
if (Arr::get($request, 'properties.bookmark-of') || Arr::get($request, 'bookmark-of')) {
return resolve(BookmarkService::class)->create($request)->longurl;
}
if (Arr::get($request, 'properties.name') || Arr::get($request, 'name')) {
return resolve(ArticleService::class)->create($request)->longurl;
}
return resolve(NoteService::class)->create($request, $client)->longurl;
}
}

View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace App\Services\Micropub;
interface MicropubHandlerInterface
{
public function handle(array $data);
}

View file

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Services\Micropub;
use App\Exceptions\MicropubHandlerException;
class MicropubHandlerRegistry
{
/**
* @var MicropubHandlerInterface[]
*/
protected array $handlers = [];
public function register(string $type, MicropubHandlerInterface $handler): self
{
$this->handlers[$type] = $handler;
return $this;
}
/**
* @throws MicropubHandlerException
*/
public function getHandler(string $type): MicropubHandlerInterface
{
if (! isset($this->handlers[$type])) {
throw new MicropubHandlerException("No handler registered for '{$type}'");
}
return $this->handlers[$type];
}
}

View file

@ -4,23 +4,35 @@ declare(strict_types=1);
namespace App\Services\Micropub; namespace App\Services\Micropub;
use App\Exceptions\InvalidTokenScopeException;
use App\Models\Media; use App\Models\Media;
use App\Models\Note; use App\Models\Note;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class UpdateService /*
* @todo Implement this properly
*/
class UpdateHandler implements MicropubHandlerInterface
{ {
/** /**
* Process a micropub request to update an entry. * @throws InvalidTokenScopeException
*/ */
public function process(array $request): JsonResponse public function handle(array $data)
{ {
$urlPath = parse_url(Arr::get($request, 'url'), PHP_URL_PATH); $scopes = $data['token_data']['scope'];
if (is_string($scopes)) {
$scopes = explode(' ', $scopes);
}
//is it a note we are updating? if (! in_array('update', $scopes, true)) {
throw new InvalidTokenScopeException;
}
$urlPath = parse_url(Arr::get($data, 'url'), PHP_URL_PATH);
// is it a note we are updating?
if (mb_substr($urlPath, 1, 5) !== 'notes') { if (mb_substr($urlPath, 1, 5) !== 'notes') {
return response()->json([ return response()->json([
'error' => 'invalid', 'error' => 'invalid',
@ -30,16 +42,16 @@ class UpdateService
try { try {
$note = Note::nb60(basename($urlPath))->firstOrFail(); $note = Note::nb60(basename($urlPath))->firstOrFail();
} catch (ModelNotFoundException $exception) { } catch (ModelNotFoundException) {
return response()->json([ return response()->json([
'error' => 'invalid_request', 'error' => 'invalid_request',
'error_description' => 'No known note with given ID', 'error_description' => 'No known note with given ID',
], 404); ], 404);
} }
//got the note, are we dealing with a “replace” request? // got the note, are we dealing with a “replace” request?
if (Arr::get($request, 'replace')) { if (Arr::get($data, 'replace')) {
foreach (Arr::get($request, 'replace') as $property => $value) { foreach (Arr::get($data, 'replace') as $property => $value) {
if ($property === 'content') { if ($property === 'content') {
$note->note = $value[0]; $note->note = $value[0];
} }
@ -59,14 +71,14 @@ class UpdateService
} }
$note->save(); $note->save();
return response()->json([ return [
'response' => 'updated', 'response' => 'updated',
]); ];
} }
//how about “add” // how about “add”
if (Arr::get($request, 'add')) { if (Arr::get($data, 'add')) {
foreach (Arr::get($request, 'add') as $property => $value) { foreach (Arr::get($data, 'add') as $property => $value) {
if ($property === 'syndication') { if ($property === 'syndication') {
foreach ($value as $syndicationURL) { foreach ($value as $syndicationURL) {
if (Str::startsWith($syndicationURL, 'https://www.facebook.com')) { if (Str::startsWith($syndicationURL, 'https://www.facebook.com')) {
@ -83,7 +95,7 @@ class UpdateService
if ($property === 'photo') { if ($property === 'photo') {
foreach ($value as $photoURL) { foreach ($value as $photoURL) {
if (Str::startsWith($photoURL, 'https://')) { if (Str::startsWith($photoURL, 'https://')) {
$media = new Media(); $media = new Media;
$media->path = $photoURL; $media->path = $photoURL;
$media->type = 'image'; $media->type = 'image';
$media->save(); $media->save();

View file

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Services; namespace App\Services;
use App\Jobs\SendWebMentions; use App\Jobs\SendWebMentions;
use App\Jobs\SyndicateNoteToBluesky;
use App\Jobs\SyndicateNoteToMastodon; use App\Jobs\SyndicateNoteToMastodon;
use App\Models\Media; use App\Models\Media;
use App\Models\Note; use App\Models\Note;
@ -13,45 +14,52 @@ use App\Models\SyndicationTarget;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class NoteService extends Service class NoteService
{ {
/** /**
* Create a new note. * Create a new note.
*/ */
public function create(array $request, ?string $client = null): Note public function create(array $data): Note
{ {
// Get the content we want to save
if (is_string($data['content'])) {
$content = $data['content'];
} elseif (isset($data['content']['html'])) {
$content = $data['content']['html'];
} else {
$content = null;
}
$note = Note::create( $note = Note::create(
[ [
'note' => $this->getDataByKey($request, 'content'), 'note' => $content,
'in_reply_to' => $this->getDataByKey($request, 'in-reply-to'), 'in_reply_to' => $data['in-reply-to'],
'client_id' => $client, 'client_id' => $data['token_data']['client_id'],
] ]
); );
if ($this->getPublished($request)) { if ($published = $this->getPublished($data)) {
$note->created_at = $note->updated_at = $this->getPublished($request); $note->created_at = $note->updated_at = $published;
} }
$note->location = $this->getLocation($request); $note->location = $this->getLocation($data);
if ($this->getCheckin($request)) { if ($this->getCheckin($data)) {
$note->place()->associate($this->getCheckin($request)); $note->place()->associate($this->getCheckin($data));
$note->swarm_url = $this->getSwarmUrl($request); $note->swarm_url = $this->getSwarmUrl($data);
}
$note->instagram_url = $this->getInstagramUrl($request);
foreach ($this->getMedia($request) as $media) {
$note->media()->save($media);
} }
//
// $note->instagram_url = $this->getInstagramUrl($request);
//
// foreach ($this->getMedia($request) as $media) {
// $note->media()->save($media);
// }
$note->save(); $note->save();
dispatch(new SendWebMentions($note)); dispatch(new SendWebMentions($note));
if (in_array('mastodon', $this->getSyndicationTargets($request), true)) { $this->dispatchSyndicationJobs($note, $data);
dispatch(new SyndicateNoteToMastodon($note));
}
return $note; return $note;
} }
@ -59,14 +67,10 @@ class NoteService extends Service
/** /**
* Get the published time from the request to create a new note. * Get the published time from the request to create a new note.
*/ */
private function getPublished(array $request): ?string private function getPublished(array $data): ?string
{ {
if (Arr::get($request, 'properties.published.0')) { if ($data['published']) {
return carbon(Arr::get($request, 'properties.published.0')) return carbon($data['published'])->toDateTimeString();
->toDateTimeString();
}
if (Arr::get($request, 'published')) {
return carbon(Arr::get($request, 'published'))->toDateTimeString();
} }
return null; return null;
@ -75,12 +79,13 @@ class NoteService extends Service
/** /**
* Get the location data from the request to create a new note. * Get the location data from the request to create a new note.
*/ */
private function getLocation(array $request): ?string private function getLocation(array $data): ?string
{ {
$location = Arr::get($request, 'properties.location.0') ?? Arr::get($request, 'location'); $location = Arr::get($data, 'location');
if (is_string($location) && str_starts_with($location, 'geo:')) { if (is_string($location) && str_starts_with($location, 'geo:')) {
preg_match_all( preg_match_all(
'/([0-9\.\-]+)/', '/([0-9.\-]+)/',
$location, $location,
$matches $matches
); );
@ -94,9 +99,9 @@ class NoteService extends Service
/** /**
* Get the checkin data from the request to create a new note. This will be a Place. * Get the checkin data from the request to create a new note. This will be a Place.
*/ */
private function getCheckin(array $request): ?Place private function getCheckin(array $data): ?Place
{ {
$location = Arr::get($request, 'location'); $location = Arr::get($data, 'location');
if (is_string($location) && Str::startsWith($location, config('app.url'))) { if (is_string($location) && Str::startsWith($location, config('app.url'))) {
return Place::where( return Place::where(
'slug', 'slug',
@ -108,12 +113,12 @@ class NoteService extends Service
) )
)->first(); )->first();
} }
if (Arr::get($request, 'checkin')) { if (Arr::get($data, 'checkin')) {
try { try {
$place = resolve(PlaceService::class)->createPlaceFromCheckin( $place = resolve(PlaceService::class)->createPlaceFromCheckin(
Arr::get($request, 'checkin') Arr::get($data, 'checkin')
); );
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException) {
return null; return null;
} }
@ -137,34 +142,47 @@ class NoteService extends Service
/** /**
* Get the Swarm URL from the syndication data in the request to create a new note. * Get the Swarm URL from the syndication data in the request to create a new note.
*/ */
private function getSwarmUrl(array $request): ?string private function getSwarmUrl(array $data): ?string
{ {
if (str_contains(Arr::get($request, 'properties.syndication.0', ''), 'swarmapp')) { $syndication = Arr::get($data, 'syndication');
return Arr::get($request, 'properties.syndication.0'); if ($syndication === null) {
return null;
}
if (str_contains($syndication, 'swarmapp')) {
return $syndication;
} }
return null; return null;
} }
/** /**
* Get the syndication targets from the request to create a new note. * Dispatch syndication jobs based on the request data.
*/ */
private function getSyndicationTargets(array $request): array private function dispatchSyndicationJobs(Note $note, array $request): void
{ {
$syndication = []; // If no syndication targets are specified, return early
$mpSyndicateTo = Arr::get($request, 'mp-syndicate-to') ?? Arr::get($request, 'properties.mp-syndicate-to'); if (empty($request['mp-syndicate-to'])) {
$mpSyndicateTo = Arr::wrap($mpSyndicateTo); return;
foreach ($mpSyndicateTo as $uid) {
$target = SyndicationTarget::where('uid', $uid)->first();
if ($target && $target->service_name === 'Twitter') {
$syndication[] = 'twitter';
}
if ($target && $target->service_name === 'Mastodon') {
$syndication[] = 'mastodon';
}
} }
return $syndication; // Get the configured syndication targets
$syndicationTargets = SyndicationTarget::all();
foreach ($syndicationTargets as $target) {
// Check if the target is in the request data
if (in_array($target->uid, $request['mp-syndicate-to'], true)) {
// Dispatch the appropriate job based on the target service name
switch ($target->service_name) {
case 'Mastodon':
dispatch(new SyndicateNoteToMastodon($note));
break;
case 'Bluesky':
dispatch(new SyndicateNoteToBluesky($note));
break;
}
}
}
} }
/** /**

View file

@ -14,8 +14,8 @@ class PlaceService
*/ */
public function createPlace(array $data): Place public function createPlace(array $data): Place
{ {
//obviously a place needs a lat/lng, but this could be sent in a geo-url // obviously a place needs a lat/lng, but this could be sent in a geo-url
//if no geo array key, we assume the array already has lat/lng values // if no geo array key, we assume the array already has lat/lng values
if (array_key_exists('geo', $data) && $data['geo'] !== null) { if (array_key_exists('geo', $data) && $data['geo'] !== null) {
preg_match_all( preg_match_all(
'/([0-9\.\-]+)/', '/([0-9\.\-]+)/',
@ -25,7 +25,7 @@ class PlaceService
$data['latitude'] = $matches[0][0]; $data['latitude'] = $matches[0][0];
$data['longitude'] = $matches[0][1]; $data['longitude'] = $matches[0][1];
} }
$place = new Place(); $place = new Place;
$place->name = $data['name']; $place->name = $data['name'];
$place->description = $data['description']; $place->description = $data['description'];
$place->latitude = $data['latitude']; $place->latitude = $data['latitude'];
@ -40,7 +40,7 @@ class PlaceService
*/ */
public function createPlaceFromCheckin(array $checkin): Place public function createPlaceFromCheckin(array $checkin): Place
{ {
//check if the place exists if from swarm // check if the place exists if from swarm
if (Arr::has($checkin, 'properties.url')) { if (Arr::has($checkin, 'properties.url')) {
$place = Place::whereExternalURL(Arr::get($checkin, 'properties.url.0'))->get(); $place = Place::whereExternalURL(Arr::get($checkin, 'properties.url.0'))->get();
if (count($place) === 1) { if (count($place) === 1) {
@ -53,7 +53,7 @@ class PlaceService
if (Arr::has($checkin, 'properties.latitude') === false) { if (Arr::has($checkin, 'properties.latitude') === false) {
throw new \InvalidArgumentException('Missing required longitude/latitude'); throw new \InvalidArgumentException('Missing required longitude/latitude');
} }
$place = new Place(); $place = new Place;
$place->name = Arr::get($checkin, 'properties.name.0'); $place->name = Arr::get($checkin, 'properties.name.0');
$place->external_urls = Arr::get($checkin, 'properties.url.0'); $place->external_urls = Arr::get($checkin, 'properties.url.0');
$place->latitude = Arr::get($checkin, 'properties.latitude.0'); $place->latitude = Arr::get($checkin, 'properties.latitude.0');

View file

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Services;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
abstract class Service
{
abstract public function create(array $request, ?string $client = null): Model;
protected function getDataByKey(array $request, string $key): ?string
{
if (Arr::get($request, "properties.{$key}.0.html")) {
return Arr::get($request, "properties.{$key}.0.html");
}
if (is_string(Arr::get($request, "properties.{$key}.0"))) {
return Arr::get($request, "properties.{$key}.0");
}
if (is_string(Arr::get($request, "properties.{$key}"))) {
return Arr::get($request, "properties.{$key}");
}
return Arr::get($request, $key);
}
}

View file

@ -7,7 +7,6 @@ namespace App\Services;
use App\Jobs\AddClientToDatabase; use App\Jobs\AddClientToDatabase;
use DateTimeImmutable; use DateTimeImmutable;
use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Token;
class TokenService class TokenService
{ {
@ -19,7 +18,7 @@ class TokenService
$config = resolve(Configuration::class); $config = resolve(Configuration::class);
$token = $config->builder() $token = $config->builder()
->issuedAt(new DateTimeImmutable()) ->issuedAt(new DateTimeImmutable)
->withClaim('client_id', $data['client_id']) ->withClaim('client_id', $data['client_id'])
->withClaim('me', $data['me']) ->withClaim('me', $data['me'])
->withClaim('scope', $data['scope']) ->withClaim('scope', $data['scope'])
@ -30,20 +29,4 @@ class TokenService
return $token->toString(); return $token->toString();
} }
/**
* Check the token signature is valid.
*/
public function validateToken(string $bearerToken): Token
{
$config = resolve('Lcobucci\JWT\Configuration');
$token = $config->parser()->parse($bearerToken);
$constraints = $config->validationConstraints();
$config->validator()->assert($token, ...$constraints);
return $token;
}
} }

View file

@ -1,6 +1,6 @@
<?php <?php
use App\Http\Middleware\CSPHeader; use App\Http\Middleware\LinkHeadersMiddleware;
use Illuminate\Foundation\Application; use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions; use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware; use Illuminate\Foundation\Configuration\Middleware;
@ -12,7 +12,16 @@ return Application::configure(basePath: dirname(__DIR__))
health: '/up', health: '/up',
) )
->withMiddleware(function (Middleware $middleware) { ->withMiddleware(function (Middleware $middleware) {
$middleware->append(CSPHeader::class); $middleware
->append(LinkHeadersMiddleware::class)
->validateCsrfTokens(except: [
'auth', // This is the IndieAuth auth endpoint
'token', // This is the IndieAuth token endpoint
'api/post',
'api/media',
'micropub/places',
'webmention',
]);
}) })
->withExceptions(function (Exceptions $exceptions) { ->withExceptions(function (Exceptions $exceptions) {
// //

View file

@ -3,4 +3,5 @@
return [ return [
App\Providers\AppServiceProvider::class, App\Providers\AppServiceProvider::class,
App\Providers\HorizonServiceProvider::class, App\Providers\HorizonServiceProvider::class,
App\Providers\MicropubServiceProvider::class,
]; ];

View file

@ -1,7 +1,8 @@
{ {
"$schema": "https://getcomposer.org/schema.json",
"name": "jonnybarnes/jonnybarnes.uk", "name": "jonnybarnes/jonnybarnes.uk",
"type": "project", "type": "project",
"description": "The code for jonnybarnes.uk, based on Laravel 10", "description": "The code for jonnybarnes.uk, based on Laravel 11",
"keywords": ["laravel", "framework", "indieweb"], "keywords": ["laravel", "framework", "indieweb"],
"license": "CC0-1.0", "license": "CC0-1.0",
"require": { "require": {
@ -10,14 +11,15 @@
"ext-intl": "*", "ext-intl": "*",
"ext-json": "*", "ext-json": "*",
"ext-pgsql": "*", "ext-pgsql": "*",
"cviebrock/eloquent-sluggable": "^11.0", "ext-sodium": "*",
"cviebrock/eloquent-sluggable": "^12.0",
"guzzlehttp/guzzle": "^7.2", "guzzlehttp/guzzle": "^7.2",
"indieauth/client": "^1.1", "indieauth/client": "^1.1",
"intervention/image": "^3", "intervention/image": "^3",
"jonnybarnes/indieweb": "~0.2", "jonnybarnes/indieweb": "~0.2",
"jonnybarnes/webmentions-parser": "~0.5", "jonnybarnes/webmentions-parser": "~0.5",
"jublonet/codebird-php": "4.0.0-beta.1", "jublonet/codebird-php": "4.0.0-beta.1",
"laravel/framework": "^11.0", "laravel/framework": "^12.0",
"laravel/horizon": "^5.0", "laravel/horizon": "^5.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
"laravel/scout": "^10.1", "laravel/scout": "^10.1",
@ -26,26 +28,29 @@
"league/commonmark": "^2.0", "league/commonmark": "^2.0",
"league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-aws-s3-v3": "^3.0",
"mf2/mf2": "~0.3", "mf2/mf2": "~0.3",
"phpdocumentor/reflection-docblock": "^5.3",
"spatie/commonmark-highlighter": "^3.0", "spatie/commonmark-highlighter": "^3.0",
"spatie/laravel-ignition": "^2.1", "spatie/laravel-ignition": "^2.1",
"symfony/html-sanitizer": "^7.0", "symfony/html-sanitizer": "^7.0",
"web-auth/webauthn-lib": "^4.7" "symfony/property-access": "^7.0",
"symfony/serializer": "^7.0",
"web-auth/webauthn-lib": "^5.0"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-debugbar": "^3.0", "barryvdh/laravel-debugbar": "^3.0",
"barryvdh/laravel-ide-helper": "^3.0", "barryvdh/laravel-ide-helper": "^3.0",
"fakerphp/faker": "^1.9.2", "fakerphp/faker": "^1.9.2",
"laravel/dusk": "^8.0", "laravel/dusk": "^8.0",
"laravel/pail": "^1.2",
"laravel/pint": "^1.0", "laravel/pint": "^1.0",
"laravel/sail": "^1.18", "laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4", "mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^8.1", "nunomaduro/collision": "^8.1",
"openai-php/client": "^0.8.0", "openai-php/client": "^0.10.1",
"phpunit/php-code-coverage": "^10.0", "phpunit/php-code-coverage": "^11.0",
"phpunit/phpunit": "^10.1", "phpunit/phpunit": "^11.0",
"psalm/plugin-laravel": "^2.8",
"spatie/laravel-ray": "^1.12", "spatie/laravel-ray": "^1.12",
"vimeo/psalm": "^5.0" "spatie/x-ray": "^1.2"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
@ -74,7 +79,13 @@
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
], ],
"post-create-project-cmd": [ "post-create-project-cmd": [
"@php artisan key:generate --ansi" "@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
] ]
}, },
"extra": { "extra": {

5655
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -65,7 +65,7 @@ return [
| |
*/ */
'timezone' => env('APP_TIMEZONE', 'UTC'), 'timezone' => 'UTC',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------

View file

@ -15,4 +15,17 @@ return [
'mastodon_token' => env('BRIDGY_MASTODON_TOKEN'), 'mastodon_token' => env('BRIDGY_MASTODON_TOKEN'),
/*
|--------------------------------------------------------------------------
| Bluesky Token
|--------------------------------------------------------------------------
|
| When syndicating posts to Bluesky using Brid.gys Micropub endpoint, we
| need to provide an access token. This token can be generated by going to
| https://brid.gy/bluesky and clicking the “Get token” button.
|
*/
'bluesky_token' => env('BRIDGY_BLUESKY_TOKEN'),
]; ];

View file

@ -37,6 +37,9 @@ return [
'database' => env('DB_DATABASE', database_path('database.sqlite')), 'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '', 'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
'busy_timeout' => null,
'journal_mode' => null,
'synchronous' => null,
], ],
'mysql' => [ 'mysql' => [
@ -145,6 +148,7 @@ return [
'options' => [ 'options' => [
'cluster' => env('REDIS_CLUSTER', 'redis'), 'cluster' => env('REDIS_CLUSTER', 'redis'),
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
'persistent' => env('REDIS_PERSISTENT', false),
], ],
'default' => [ 'default' => [

View file

@ -119,7 +119,7 @@ return [
'full_log' => false, 'full_log' => false,
], ],
'views' => [ 'views' => [
'data' => false, //Note: Can slow down the application, because the data can be quite large.. 'data' => false, // Note: Can slow down the application, because the data can be quite large..
], ],
'route' => [ 'route' => [
'label' => true, // show complete route on bar 'label' => true, // show complete route on bar

View file

@ -32,8 +32,10 @@ return [
'local' => [ 'local' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app'), 'root' => storage_path('app/private'),
'serve' => true,
'throw' => false, 'throw' => false,
'report' => false,
], ],
'public' => [ 'public' => [
@ -42,6 +44,7 @@ return [
'url' => env('APP_URL').'/storage', 'url' => env('APP_URL').'/storage',
'visibility' => 'public', 'visibility' => 'public',
'throw' => false, 'throw' => false,
'report' => false,
], ],
's3' => [ 's3' => [
@ -54,6 +57,7 @@ return [
'endpoint' => env('AWS_ENDPOINT'), 'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false, 'throw' => false,
'report' => false,
], ],
], ],

View file

@ -127,6 +127,10 @@ return [
'path' => storage_path('logs/laravel.log'), 'path' => storage_path('logs/laravel.log'),
], ],
'flare' => [
'driver' => 'flare',
],
], ],
]; ];

Some files were not shown because too many files have changed in this diff Show more