mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-13 19:09:50 +00:00
chore: update team invitation handling
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use App\Providers\RouteServiceProvider;
|
||||
use Illuminate\Auth\Events\Verified;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -98,23 +99,39 @@ class Controller extends BaseController
|
||||
{
|
||||
$token = request()->get('token');
|
||||
if ($token) {
|
||||
$decrypted = Crypt::decryptString($token);
|
||||
$email = str($decrypted)->before('@@@');
|
||||
$password = str($decrypted)->after('@@@');
|
||||
try {
|
||||
$decrypted = Crypt::decryptString($token);
|
||||
} catch (DecryptException) {
|
||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||
}
|
||||
|
||||
if (! str_contains($decrypted, '@@@')) {
|
||||
return redirect()->route('login')->with('error', 'Invalid credentials.');
|
||||
}
|
||||
|
||||
[$email, $password] = explode('@@@', $decrypted, 2);
|
||||
$email = Str::lower($email);
|
||||
$user = User::whereEmail($email)->first();
|
||||
if (! $user) {
|
||||
return redirect()->route('login');
|
||||
}
|
||||
|
||||
$invitation = TeamInvitation::whereEmail($email)->first();
|
||||
if (! $invitation || ! $invitation->isValid()) {
|
||||
return redirect()->route('login')->with('error', 'Invitation has expired or been revoked.');
|
||||
}
|
||||
|
||||
if (Hash::check($password, $user->password)) {
|
||||
$invitation = TeamInvitation::whereEmail($email);
|
||||
if ($invitation->exists()) {
|
||||
$team = $invitation->first()->team;
|
||||
$user->teams()->attach($team->id, ['role' => $invitation->first()->role]);
|
||||
$invitation->delete();
|
||||
} else {
|
||||
$team = $user->teams()->first();
|
||||
$team = $invitation->team;
|
||||
if (! $user->teams()->where('team_id', $team->id)->exists()) {
|
||||
$user->teams()->attach($team->id, ['role' => $invitation->role]);
|
||||
}
|
||||
$invitation->delete();
|
||||
|
||||
Auth::login($user);
|
||||
$user->forceFill([
|
||||
'password' => Hash::make(Str::random(64)),
|
||||
])->save();
|
||||
session(['currentTeam' => $team]);
|
||||
|
||||
return redirect()->route('dashboard');
|
||||
|
||||
@@ -1620,7 +1620,7 @@
|
||||
"garage": {
|
||||
"documentation": "https://garagehq.deuxfleurs.fr/documentation/?utm_source=coolify.io",
|
||||
"slogan": "Garage is an S3-compatible distributed object storage service designed for self-hosting.",
|
||||
"compose": "c2VydmljZXM6CiAgZ2FyYWdlOgogICAgaW1hZ2U6ICdkeGZscnMvZ2FyYWdlOnYyLjEuMCcKICAgIGVudmlyb25tZW50OgogICAgICAtIEdBUkFHRV9TM19BUElfVVJMPSRHQVJBR0VfUzNfQVBJX1VSTAogICAgICAtIEdBUkFHRV9XRUJfVVJMPSRHQVJBR0VfV0VCX1VSTAogICAgICAtIEdBUkFHRV9BRE1JTl9VUkw9JEdBUkFHRV9BRE1JTl9VUkwKICAgICAgLSAnR0FSQUdFX1JQQ19TRUNSRVQ9JHtTRVJWSUNFX0hFWF8zMl9SUENTRUNSRVR9JwogICAgICAtIEdBUkFHRV9BRE1JTl9UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9HQVJBR0UKICAgICAgLSBHQVJBR0VfTUVUUklDU19UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9HQVJBR0VNRVRSSUNTCiAgICAgIC0gR0FSQUdFX0FMTE9XX1dPUkxEX1JFQURBQkxFX1NFQ1JFVFM9dHJ1ZQogICAgICAtICdSVVNUX0xPRz0ke1JVU1RfTE9HOi1nYXJhZ2U9aW5mb30nCiAgICB2b2x1bWVzOgogICAgICAtICdnYXJhZ2UtbWV0YTovdmFyL2xpYi9nYXJhZ2UvbWV0YScKICAgICAgLSAnZ2FyYWdlLWRhdGE6L3Zhci9saWIvZ2FyYWdlL2RhdGEnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2dhcmFnZS50b21sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2dhcmFnZS50b21sCiAgICAgICAgY29udGVudDogIm1ldGFkYXRhX2RpciA9IFwiL3Zhci9saWIvZ2FyYWdlL21ldGFcIlxuZGF0YV9kaXIgPSBcIi92YXIvbGliL2dhcmFnZS9kYXRhXCJcbmRiX2VuZ2luZSA9IFwibG1kYlwiXG5cbnJlcGxpY2F0aW9uX2ZhY3RvciA9IDFcbmNvbnNpc3RlbmN5X21vZGUgPSBcImNvbnNpc3RlbnRcIlxuXG5jb21wcmVzc2lvbl9sZXZlbCA9IDFcbmJsb2NrX3NpemUgPSBcIjFNXCJcblxucnBjX2JpbmRfYWRkciA9IFwiWzo6XTozOTAxXCJcbnJwY19zZWNyZXRfZmlsZSA9IFwiZW52OkdBUkFHRV9SUENfU0VDUkVUXCJcbmJvb3RzdHJhcF9wZWVycyA9IFtdXG5cbltzM19hcGldXG5zM19yZWdpb24gPSBcImdhcmFnZVwiXG5hcGlfYmluZF9hZGRyID0gXCJbOjpdOjM5MDBcIlxucm9vdF9kb21haW4gPSBcIi5zMy5nYXJhZ2UubG9jYWxob3N0XCJcblxuW3MzX3dlYl1cbmJpbmRfYWRkciA9IFwiWzo6XTozOTAyXCJcbnJvb3RfZG9tYWluID0gXCIud2ViLmdhcmFnZS5sb2NhbGhvc3RcIlxuXG5bYWRtaW5dXG5hcGlfYmluZF9hZGRyID0gXCJbOjpdOjM5MDNcIlxuYWRtaW5fdG9rZW5fZmlsZSA9IFwiZW52OkdBUkFHRV9BRE1JTl9UT0tFTlwiXG5tZXRyaWNzX3Rva2VuX2ZpbGUgPSBcImVudjpHQVJBR0VfTUVUUklDU19UT0tFTlwiIgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9nYXJhZ2UKICAgICAgICAtIHN0YXRzCiAgICAgICAgLSAnLWEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQo=",
|
||||
"compose": "c2VydmljZXM6CiAgZ2FyYWdlOgogICAgaW1hZ2U6ICdkeGZscnMvZ2FyYWdlOnYyLjEuMCcKICAgIGVudmlyb25tZW50OgogICAgICAtIEdBUkFHRV9TM19BUElfVVJMPSRHQVJBR0VfUzNfQVBJX1VSTAogICAgICAtIEdBUkFHRV9XRUJfVVJMPSRHQVJBR0VfV0VCX1VSTAogICAgICAtIEdBUkFHRV9BRE1JTl9VUkw9JEdBUkFHRV9BRE1JTl9VUkwKICAgICAgLSAnR0FSQUdFX1JQQ19TRUNSRVQ9JHtTRVJWSUNFX0hFWF82NF9SUENTRUNSRVR9JwogICAgICAtIEdBUkFHRV9BRE1JTl9UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9HQVJBR0UKICAgICAgLSBHQVJBR0VfTUVUUklDU19UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9HQVJBR0VNRVRSSUNTCiAgICAgIC0gR0FSQUdFX0FMTE9XX1dPUkxEX1JFQURBQkxFX1NFQ1JFVFM9dHJ1ZQogICAgICAtICdSVVNUX0xPRz0ke1JVU1RfTE9HOi1nYXJhZ2U9aW5mb30nCiAgICB2b2x1bWVzOgogICAgICAtICdnYXJhZ2UtbWV0YTovdmFyL2xpYi9nYXJhZ2UvbWV0YScKICAgICAgLSAnZ2FyYWdlLWRhdGE6L3Zhci9saWIvZ2FyYWdlL2RhdGEnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2dhcmFnZS50b21sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2dhcmFnZS50b21sCiAgICAgICAgY29udGVudDogIm1ldGFkYXRhX2RpciA9IFwiL3Zhci9saWIvZ2FyYWdlL21ldGFcIlxuZGF0YV9kaXIgPSBcIi92YXIvbGliL2dhcmFnZS9kYXRhXCJcbmRiX2VuZ2luZSA9IFwibG1kYlwiXG5cbnJlcGxpY2F0aW9uX2ZhY3RvciA9IDFcbmNvbnNpc3RlbmN5X21vZGUgPSBcImNvbnNpc3RlbnRcIlxuXG5jb21wcmVzc2lvbl9sZXZlbCA9IDFcbmJsb2NrX3NpemUgPSBcIjFNXCJcblxucnBjX2JpbmRfYWRkciA9IFwiWzo6XTozOTAxXCJcbnJwY19zZWNyZXRfZmlsZSA9IFwiZW52OkdBUkFHRV9SUENfU0VDUkVUXCJcbmJvb3RzdHJhcF9wZWVycyA9IFtdXG5cbltzM19hcGldXG5zM19yZWdpb24gPSBcImdhcmFnZVwiXG5hcGlfYmluZF9hZGRyID0gXCJbOjpdOjM5MDBcIlxucm9vdF9kb21haW4gPSBcIi5zMy5nYXJhZ2UubG9jYWxob3N0XCJcblxuW3MzX3dlYl1cbmJpbmRfYWRkciA9IFwiWzo6XTozOTAyXCJcbnJvb3RfZG9tYWluID0gXCIud2ViLmdhcmFnZS5sb2NhbGhvc3RcIlxuXG5bYWRtaW5dXG5hcGlfYmluZF9hZGRyID0gXCJbOjpdOjM5MDNcIlxuYWRtaW5fdG9rZW5fZmlsZSA9IFwiZW52OkdBUkFHRV9BRE1JTl9UT0tFTlwiXG5tZXRyaWNzX3Rva2VuX2ZpbGUgPSBcImVudjpHQVJBR0VfTUVUUklDU19UT0tFTlwiIgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9nYXJhZ2UKICAgICAgICAtIHN0YXRzCiAgICAgICAgLSAnLWEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQo=",
|
||||
"tags": [
|
||||
"object",
|
||||
"storage",
|
||||
@@ -1666,7 +1666,7 @@
|
||||
"gitea-runner": {
|
||||
"documentation": "https://github.com/go-gitea/gitea?utm_source=coolify.io",
|
||||
"slogan": "Gitea Actions runner for docker",
|
||||
"compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC42JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC43JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"tags": [
|
||||
"gitea",
|
||||
"actions",
|
||||
@@ -2007,6 +2007,24 @@
|
||||
"minversion": "0.0.0",
|
||||
"port": "80"
|
||||
},
|
||||
"healthchecks": {
|
||||
"documentation": "https://healthchecks.io/docs?utm_source=coolify.io",
|
||||
"slogan": "Healthchecks is a cron job monitoring service. It listens for HTTP requests and email messages from your cron jobs and scheduled tasks.",
|
||||
"compose": "c2VydmljZXM6CiAgaGVhbHRoY2hlY2tzOgogICAgaW1hZ2U6ICdoZWFsdGhjaGVja3MvaGVhbHRoY2hlY2tzOnY0LjInCiAgICBjb250YWluZXJfbmFtZTogaGVhbHRoY2hlY2tzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX1VSTF9IRUFMVEhDSEVDS1NfODAwMAogICAgICAtIERCPXNxbGl0ZQogICAgICAtIERCX05BTUU9L2RhdGEvaGMuc3FsaXRlCiAgICAgIC0gJ0FMTE9XRURfSE9TVFM9JHtBTExPV0VEX0hPU1RTOi0qfScKICAgICAgLSAnUkVHSVNUUkFUSU9OX09QRU49JHtSRUdJU1RSQVRJT05fT1BFTjotVHJ1ZX0nCiAgICAgIC0gJ0RFQlVHPSR7REVCVUc6LUZhbHNlfScKICAgICAgLSAnU0VDUkVUX0tFWT0ke1NFQ1JFVF9LRVk6PyR7U0VSVklDRV9QQVNTV09SRF82NF9IRUFMVEhDSEVDS1N9fScKICAgICAgLSAnU0lURV9ST09UPSR7U0VSVklDRV9VUkxfSEVBTFRIQ0hFQ0tTfScKICAgICAgLSAnREVGQVVMVF9GUk9NX0VNQUlMPSR7REVGQVVMVF9GUk9NX0VNQUlMOi1maXhtZS1lbWFpbC1hZGRyZXNzLWhlcmV9JwogICAgICAtICdFTUFJTF9IT1NUPSR7RU1BSUxfSE9TVDotbXktc210cC1zZXJ2ZXItaGVyZS5jb219JwogICAgICAtICdFTUFJTF9QT1JUPSR7RU1BSUxfUE9SVDotNDY1fScKICAgICAgLSAnRU1BSUxfSE9TVF9VU0VSPSR7RU1BSUxfSE9TVF9VU0VSOi1teV91c2VybmFtZX0nCiAgICAgIC0gJ0VNQUlMX0hPU1RfUEFTU1dPUkQ9JHtFTUFJTF9IT1NUX1BBU1NXT1JEOi1teXBhc3N3b3JkfScKICAgICAgLSAnRU1BSUxfVVNFX1NTTD0ke0VNQUlMX1VTRV9TU0w6LVRydWV9JwogICAgICAtICdFTUFJTF9VU0VfVExTPSR7RU1BSUxfVVNFX1RMUzotRmFsc2V9JwogICAgdm9sdW1lczoKICAgICAgLSAnaGVhbHRoY2hlY2tzLWRhdGE6L2RhdGEnCg==",
|
||||
"tags": [
|
||||
"monitoring",
|
||||
"status",
|
||||
"performance",
|
||||
"web",
|
||||
"services",
|
||||
"applications",
|
||||
"real-time"
|
||||
],
|
||||
"category": "monitoring",
|
||||
"logo": "svgs/healthchecks.webp",
|
||||
"minversion": "0.0.0",
|
||||
"port": "80000"
|
||||
},
|
||||
"heimdall": {
|
||||
"documentation": "https://github.com/linuxserver/Heimdall?utm_source=coolify.io",
|
||||
"slogan": "Heimdall is a dashboard for managing and organizing your server applications.",
|
||||
|
||||
@@ -1620,7 +1620,7 @@
|
||||
"garage": {
|
||||
"documentation": "https://garagehq.deuxfleurs.fr/documentation/?utm_source=coolify.io",
|
||||
"slogan": "Garage is an S3-compatible distributed object storage service designed for self-hosting.",
|
||||
"compose": "c2VydmljZXM6CiAgZ2FyYWdlOgogICAgaW1hZ2U6ICdkeGZscnMvZ2FyYWdlOnYyLjEuMCcKICAgIGVudmlyb25tZW50OgogICAgICAtIEdBUkFHRV9TM19BUElfVVJMPSRHQVJBR0VfUzNfQVBJX1VSTAogICAgICAtIEdBUkFHRV9XRUJfVVJMPSRHQVJBR0VfV0VCX1VSTAogICAgICAtIEdBUkFHRV9BRE1JTl9VUkw9JEdBUkFHRV9BRE1JTl9VUkwKICAgICAgLSAnR0FSQUdFX1JQQ19TRUNSRVQ9JHtTRVJWSUNFX0hFWF8zMl9SUENTRUNSRVR9JwogICAgICAtIEdBUkFHRV9BRE1JTl9UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9HQVJBR0UKICAgICAgLSBHQVJBR0VfTUVUUklDU19UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9HQVJBR0VNRVRSSUNTCiAgICAgIC0gR0FSQUdFX0FMTE9XX1dPUkxEX1JFQURBQkxFX1NFQ1JFVFM9dHJ1ZQogICAgICAtICdSVVNUX0xPRz0ke1JVU1RfTE9HOi1nYXJhZ2U9aW5mb30nCiAgICB2b2x1bWVzOgogICAgICAtICdnYXJhZ2UtbWV0YTovdmFyL2xpYi9nYXJhZ2UvbWV0YScKICAgICAgLSAnZ2FyYWdlLWRhdGE6L3Zhci9saWIvZ2FyYWdlL2RhdGEnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2dhcmFnZS50b21sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2dhcmFnZS50b21sCiAgICAgICAgY29udGVudDogIm1ldGFkYXRhX2RpciA9IFwiL3Zhci9saWIvZ2FyYWdlL21ldGFcIlxuZGF0YV9kaXIgPSBcIi92YXIvbGliL2dhcmFnZS9kYXRhXCJcbmRiX2VuZ2luZSA9IFwibG1kYlwiXG5cbnJlcGxpY2F0aW9uX2ZhY3RvciA9IDFcbmNvbnNpc3RlbmN5X21vZGUgPSBcImNvbnNpc3RlbnRcIlxuXG5jb21wcmVzc2lvbl9sZXZlbCA9IDFcbmJsb2NrX3NpemUgPSBcIjFNXCJcblxucnBjX2JpbmRfYWRkciA9IFwiWzo6XTozOTAxXCJcbnJwY19zZWNyZXRfZmlsZSA9IFwiZW52OkdBUkFHRV9SUENfU0VDUkVUXCJcbmJvb3RzdHJhcF9wZWVycyA9IFtdXG5cbltzM19hcGldXG5zM19yZWdpb24gPSBcImdhcmFnZVwiXG5hcGlfYmluZF9hZGRyID0gXCJbOjpdOjM5MDBcIlxucm9vdF9kb21haW4gPSBcIi5zMy5nYXJhZ2UubG9jYWxob3N0XCJcblxuW3MzX3dlYl1cbmJpbmRfYWRkciA9IFwiWzo6XTozOTAyXCJcbnJvb3RfZG9tYWluID0gXCIud2ViLmdhcmFnZS5sb2NhbGhvc3RcIlxuXG5bYWRtaW5dXG5hcGlfYmluZF9hZGRyID0gXCJbOjpdOjM5MDNcIlxuYWRtaW5fdG9rZW5fZmlsZSA9IFwiZW52OkdBUkFHRV9BRE1JTl9UT0tFTlwiXG5tZXRyaWNzX3Rva2VuX2ZpbGUgPSBcImVudjpHQVJBR0VfTUVUUklDU19UT0tFTlwiIgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9nYXJhZ2UKICAgICAgICAtIHN0YXRzCiAgICAgICAgLSAnLWEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQo=",
|
||||
"compose": "c2VydmljZXM6CiAgZ2FyYWdlOgogICAgaW1hZ2U6ICdkeGZscnMvZ2FyYWdlOnYyLjEuMCcKICAgIGVudmlyb25tZW50OgogICAgICAtIEdBUkFHRV9TM19BUElfVVJMPSRHQVJBR0VfUzNfQVBJX1VSTAogICAgICAtIEdBUkFHRV9XRUJfVVJMPSRHQVJBR0VfV0VCX1VSTAogICAgICAtIEdBUkFHRV9BRE1JTl9VUkw9JEdBUkFHRV9BRE1JTl9VUkwKICAgICAgLSAnR0FSQUdFX1JQQ19TRUNSRVQ9JHtTRVJWSUNFX0hFWF82NF9SUENTRUNSRVR9JwogICAgICAtIEdBUkFHRV9BRE1JTl9UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9HQVJBR0UKICAgICAgLSBHQVJBR0VfTUVUUklDU19UT0tFTj0kU0VSVklDRV9QQVNTV09SRF9HQVJBR0VNRVRSSUNTCiAgICAgIC0gR0FSQUdFX0FMTE9XX1dPUkxEX1JFQURBQkxFX1NFQ1JFVFM9dHJ1ZQogICAgICAtICdSVVNUX0xPRz0ke1JVU1RfTE9HOi1nYXJhZ2U9aW5mb30nCiAgICB2b2x1bWVzOgogICAgICAtICdnYXJhZ2UtbWV0YTovdmFyL2xpYi9nYXJhZ2UvbWV0YScKICAgICAgLSAnZ2FyYWdlLWRhdGE6L3Zhci9saWIvZ2FyYWdlL2RhdGEnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2dhcmFnZS50b21sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2dhcmFnZS50b21sCiAgICAgICAgY29udGVudDogIm1ldGFkYXRhX2RpciA9IFwiL3Zhci9saWIvZ2FyYWdlL21ldGFcIlxuZGF0YV9kaXIgPSBcIi92YXIvbGliL2dhcmFnZS9kYXRhXCJcbmRiX2VuZ2luZSA9IFwibG1kYlwiXG5cbnJlcGxpY2F0aW9uX2ZhY3RvciA9IDFcbmNvbnNpc3RlbmN5X21vZGUgPSBcImNvbnNpc3RlbnRcIlxuXG5jb21wcmVzc2lvbl9sZXZlbCA9IDFcbmJsb2NrX3NpemUgPSBcIjFNXCJcblxucnBjX2JpbmRfYWRkciA9IFwiWzo6XTozOTAxXCJcbnJwY19zZWNyZXRfZmlsZSA9IFwiZW52OkdBUkFHRV9SUENfU0VDUkVUXCJcbmJvb3RzdHJhcF9wZWVycyA9IFtdXG5cbltzM19hcGldXG5zM19yZWdpb24gPSBcImdhcmFnZVwiXG5hcGlfYmluZF9hZGRyID0gXCJbOjpdOjM5MDBcIlxucm9vdF9kb21haW4gPSBcIi5zMy5nYXJhZ2UubG9jYWxob3N0XCJcblxuW3MzX3dlYl1cbmJpbmRfYWRkciA9IFwiWzo6XTozOTAyXCJcbnJvb3RfZG9tYWluID0gXCIud2ViLmdhcmFnZS5sb2NhbGhvc3RcIlxuXG5bYWRtaW5dXG5hcGlfYmluZF9hZGRyID0gXCJbOjpdOjM5MDNcIlxuYWRtaW5fdG9rZW5fZmlsZSA9IFwiZW52OkdBUkFHRV9BRE1JTl9UT0tFTlwiXG5tZXRyaWNzX3Rva2VuX2ZpbGUgPSBcImVudjpHQVJBR0VfTUVUUklDU19UT0tFTlwiIgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIC9nYXJhZ2UKICAgICAgICAtIHN0YXRzCiAgICAgICAgLSAnLWEnCiAgICAgIGludGVydmFsOiAxMHMKICAgICAgdGltZW91dDogNXMKICAgICAgcmV0cmllczogNQo=",
|
||||
"tags": [
|
||||
"object",
|
||||
"storage",
|
||||
@@ -1666,7 +1666,7 @@
|
||||
"gitea-runner": {
|
||||
"documentation": "https://github.com/go-gitea/gitea?utm_source=coolify.io",
|
||||
"slogan": "Gitea Actions runner for docker",
|
||||
"compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC42JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"compose": "c2VydmljZXM6CiAgcnVubmVyOgogICAgaW1hZ2U6ICdkb2NrZXIuaW8vZ2l0ZWEvcnVubmVyOjEuMC43JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0dJVEVBX0lOU1RBTkNFX1VSTD0ke0dJVEVBX0lOU1RBTkNFX1VSTH0nCiAgICAgIC0gJ0dJVEVBX1JVTk5FUl9SRUdJU1RSQVRJT05fVE9LRU49JHtHSVRFQV9SVU5ORVJfUkVHSVNUUkFUSU9OX1RPS0VOfScKICAgICAgLSAnR0lURUFfUlVOTkVSX05BTUU9JHtHSVRFQV9SVU5ORVJfTkFNRTotZ2l0ZWEtcnVubmVyfScKICAgICAgLSAnR0lURUFfUlVOTkVSX0xBQkVMUz0ke0dJVEVBX1JVTk5FUl9MQUJFTFM6LXVidW50dS1sYXRlc3Q6ZG9ja2VyOi8vbm9kZToyMn0nCiAgICAgIC0gJ0dJVEVBX1RPS0VOPSR7R0lURUFfVE9LRU59JwogICAgd29ya2luZ19kaXI6IC9kYXRhCiAgICB2b2x1bWVzOgogICAgICAtICdydW5uZXItZGF0YTovZGF0YScKICAgICAgLSAnL3Zhci9ydW4vZG9ja2VyLnNvY2s6L3Zhci9ydW4vZG9ja2VyLnNvY2snCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gInBzIGF1eCB8IGdyZXAgJ1tSXXVubmVyJyA+IC9kZXYvbnVsbCB8fCBleGl0IDEiCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"tags": [
|
||||
"gitea",
|
||||
"actions",
|
||||
@@ -2007,6 +2007,24 @@
|
||||
"minversion": "0.0.0",
|
||||
"port": "80"
|
||||
},
|
||||
"healthchecks": {
|
||||
"documentation": "https://healthchecks.io/docs?utm_source=coolify.io",
|
||||
"slogan": "Healthchecks is a cron job monitoring service. It listens for HTTP requests and email messages from your cron jobs and scheduled tasks.",
|
||||
"compose": "c2VydmljZXM6CiAgaGVhbHRoY2hlY2tzOgogICAgaW1hZ2U6ICdoZWFsdGhjaGVja3MvaGVhbHRoY2hlY2tzOnY0LjInCiAgICBjb250YWluZXJfbmFtZTogaGVhbHRoY2hlY2tzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fSEVBTFRIQ0hFQ0tTXzgwMDAKICAgICAgLSBEQj1zcWxpdGUKICAgICAgLSBEQl9OQU1FPS9kYXRhL2hjLnNxbGl0ZQogICAgICAtICdBTExPV0VEX0hPU1RTPSR7QUxMT1dFRF9IT1NUUzotKn0nCiAgICAgIC0gJ1JFR0lTVFJBVElPTl9PUEVOPSR7UkVHSVNUUkFUSU9OX09QRU46LVRydWV9JwogICAgICAtICdERUJVRz0ke0RFQlVHOi1GYWxzZX0nCiAgICAgIC0gJ1NFQ1JFVF9LRVk9JHtTRUNSRVRfS0VZOj8ke1NFUlZJQ0VfUEFTU1dPUkRfNjRfSEVBTFRIQ0hFQ0tTfX0nCiAgICAgIC0gJ1NJVEVfUk9PVD0ke1NFUlZJQ0VfRlFETl9IRUFMVEhDSEVDS1N9JwogICAgICAtICdERUZBVUxUX0ZST01fRU1BSUw9JHtERUZBVUxUX0ZST01fRU1BSUw6LWZpeG1lLWVtYWlsLWFkZHJlc3MtaGVyZX0nCiAgICAgIC0gJ0VNQUlMX0hPU1Q9JHtFTUFJTF9IT1NUOi1teS1zbXRwLXNlcnZlci1oZXJlLmNvbX0nCiAgICAgIC0gJ0VNQUlMX1BPUlQ9JHtFTUFJTF9QT1JUOi00NjV9JwogICAgICAtICdFTUFJTF9IT1NUX1VTRVI9JHtFTUFJTF9IT1NUX1VTRVI6LW15X3VzZXJuYW1lfScKICAgICAgLSAnRU1BSUxfSE9TVF9QQVNTV09SRD0ke0VNQUlMX0hPU1RfUEFTU1dPUkQ6LW15cGFzc3dvcmR9JwogICAgICAtICdFTUFJTF9VU0VfU1NMPSR7RU1BSUxfVVNFX1NTTDotVHJ1ZX0nCiAgICAgIC0gJ0VNQUlMX1VTRV9UTFM9JHtFTUFJTF9VU0VfVExTOi1GYWxzZX0nCiAgICB2b2x1bWVzOgogICAgICAtICdoZWFsdGhjaGVja3MtZGF0YTovZGF0YScK",
|
||||
"tags": [
|
||||
"monitoring",
|
||||
"status",
|
||||
"performance",
|
||||
"web",
|
||||
"services",
|
||||
"applications",
|
||||
"real-time"
|
||||
],
|
||||
"category": "monitoring",
|
||||
"logo": "svgs/healthchecks.webp",
|
||||
"minversion": "0.0.0",
|
||||
"port": "80000"
|
||||
},
|
||||
"heimdall": {
|
||||
"documentation": "https://github.com/linuxserver/Heimdall?utm_source=coolify.io",
|
||||
"slogan": "Heimdall is a dashboard for managing and organizing your server applications.",
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Middleware\CheckForcePasswordReset;
|
||||
use App\Http\Middleware\DecideWhatToDoWithUser;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Once;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
$this->withoutMiddleware([DecideWhatToDoWithUser::class, CheckForcePasswordReset::class]);
|
||||
Once::flush();
|
||||
Config::set('app.maintenance.driver', 'file');
|
||||
Config::set('cache.default', 'array');
|
||||
Config::set('session.driver', 'array');
|
||||
|
||||
if (! InstanceSettings::find(0)) {
|
||||
$settings = new InstanceSettings;
|
||||
$settings->id = 0;
|
||||
$settings->saveQuietly();
|
||||
}
|
||||
});
|
||||
|
||||
function createInvitationLinkFixture(array $invitationAttributes = []): array
|
||||
{
|
||||
$team = Team::factory()->create();
|
||||
$password = 'temporary-password-123';
|
||||
$user = User::factory()->create([
|
||||
'email' => $invitationAttributes['email'] ?? 'invitee@example.com',
|
||||
'password' => Hash::make($password),
|
||||
'force_password_reset' => true,
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
$token = Crypt::encryptString("{$user->email}@@@{$password}");
|
||||
$link = route('auth.link', ['token' => $token]);
|
||||
|
||||
$invitation = TeamInvitation::create(array_merge([
|
||||
'team_id' => $team->id,
|
||||
'uuid' => (string) new Cuid2(32),
|
||||
'email' => $user->email,
|
||||
'role' => 'member',
|
||||
'link' => $link,
|
||||
'via' => 'link',
|
||||
], $invitationAttributes));
|
||||
|
||||
return [$team, $user, $password, $token, $invitation];
|
||||
}
|
||||
|
||||
it('accepts a valid magic link invitation only once and rotates the temporary password', function () {
|
||||
[$team, $user, $password, $token] = createInvitationLinkFixture();
|
||||
|
||||
$this->get(route('auth.link', ['token' => $token]))
|
||||
->assertRedirect(route('dashboard'));
|
||||
|
||||
$this->assertAuthenticatedAs($user);
|
||||
$this->assertDatabaseMissing('team_invitations', ['email' => $user->email]);
|
||||
expect($user->teams()->where('team_id', $team->id)->exists())->toBeTrue();
|
||||
|
||||
$user->refresh();
|
||||
expect(Hash::check($password, $user->password))->toBeFalse();
|
||||
|
||||
auth()->logout();
|
||||
session()->flush();
|
||||
|
||||
$this->get(route('auth.link', ['token' => $token]))
|
||||
->assertRedirect(route('login'));
|
||||
|
||||
$this->assertGuest();
|
||||
});
|
||||
|
||||
it('rejects a magic link when the invitation was revoked', function () {
|
||||
[, $user, , $token, $invitation] = createInvitationLinkFixture();
|
||||
$invitation->delete();
|
||||
|
||||
$this->get(route('auth.link', ['token' => $token]))
|
||||
->assertRedirect(route('login'));
|
||||
|
||||
$this->assertGuest();
|
||||
expect($user->teams()->where('personal_team', false)->exists())->toBeFalse();
|
||||
});
|
||||
|
||||
it('rejects a magic link when the invitation expired', function () {
|
||||
[, $user, , $token, $invitation] = createInvitationLinkFixture();
|
||||
$invitation->forceFill([
|
||||
'created_at' => now()->subDays(config('constants.invitation.link.expiration_days') + 1),
|
||||
'updated_at' => now()->subDays(config('constants.invitation.link.expiration_days') + 1),
|
||||
])->save();
|
||||
|
||||
$this->get(route('auth.link', ['token' => $token]))
|
||||
->assertRedirect(route('login'));
|
||||
|
||||
$this->assertGuest();
|
||||
$this->assertDatabaseMissing('team_invitations', ['id' => $invitation->id]);
|
||||
});
|
||||
|
||||
it('rejects a malformed magic link token', function () {
|
||||
$this->get(route('auth.link', ['token' => 'not-a-valid-token']))
|
||||
->assertRedirect(route('login'));
|
||||
|
||||
$this->assertGuest();
|
||||
});
|
||||
@@ -4,8 +4,10 @@ use App\Http\Middleware\CheckForcePasswordReset;
|
||||
use App\Http\Middleware\DecideWhatToDoWithUser;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Models\TeamInvitation;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Once;
|
||||
@@ -15,6 +17,10 @@ uses(RefreshDatabase::class);
|
||||
beforeEach(function () {
|
||||
$this->withoutMiddleware([DecideWhatToDoWithUser::class, CheckForcePasswordReset::class]);
|
||||
Once::flush();
|
||||
Config::set('app.maintenance.driver', 'file');
|
||||
Config::set('cache.default', 'array');
|
||||
Config::set('session.driver', 'array');
|
||||
|
||||
if (! InstanceSettings::find(0)) {
|
||||
$settings = new InstanceSettings;
|
||||
$settings->id = 0;
|
||||
@@ -34,6 +40,14 @@ describe('invitation link login', function () {
|
||||
$user->teams()->attach($team->id, ['role' => 'member']);
|
||||
|
||||
$token = Crypt::encryptString("{$user->email}@@@{$password}");
|
||||
TeamInvitation::create([
|
||||
'team_id' => $team->id,
|
||||
'uuid' => 'email-verification-test-invitation',
|
||||
'email' => $user->email,
|
||||
'role' => 'member',
|
||||
'link' => route('auth.link', ['token' => $token]),
|
||||
'via' => 'link',
|
||||
]);
|
||||
|
||||
$this->get(route('auth.link', ['token' => $token]));
|
||||
|
||||
@@ -52,8 +66,17 @@ describe('invitation link login', function () {
|
||||
$user->teams()->attach($team->id, ['role' => 'member']);
|
||||
|
||||
$token = Crypt::encryptString("{$user->email}@@@{$password}");
|
||||
TeamInvitation::create([
|
||||
'team_id' => $team->id,
|
||||
'uuid' => 'email-verification-login-test-invitation',
|
||||
'email' => $user->email,
|
||||
'role' => 'member',
|
||||
'link' => route('auth.link', ['token' => $token]),
|
||||
'via' => 'link',
|
||||
]);
|
||||
|
||||
$this->get(route('auth.link', ['token' => $token]));
|
||||
$this->get(route('auth.link', ['token' => $token]))
|
||||
->assertRedirect(route('dashboard'));
|
||||
|
||||
expect(auth()->id())->toBe($user->id);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user