mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-08 05:43:18 +01:00
Compare commits
7 Commits
feature/po
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f1c58e216 | ||
|
|
349623d537 | ||
|
|
a9d016d773 | ||
|
|
2ebbbceefb | ||
|
|
e85058aefd | ||
|
|
a86c18ad2d | ||
|
|
f683c03ff9 |
@@ -11,6 +11,7 @@ use App\Models\Client;
|
|||||||
use App\Models\Member;
|
use App\Models\Member;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
use App\Models\OrganizationInvitation;
|
use App\Models\OrganizationInvitation;
|
||||||
|
use App\Models\Passport\RefreshToken;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\ProjectMember;
|
use App\Models\ProjectMember;
|
||||||
use App\Models\Report;
|
use App\Models\Report;
|
||||||
@@ -169,6 +170,10 @@ class DeletionService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh tokens are not linked to the user directly, so they need to be deleted via their access tokens.
|
||||||
|
// Otherwise a still-valid refresh token could be used to issue a new access token for a deleted user,
|
||||||
|
// which fails with a foreign key violation on oauth_access_tokens.user_id.
|
||||||
|
RefreshToken::query()->whereIn('access_token_id', $user->accessTokens()->pluck('id'))->delete();
|
||||||
$user->accessTokens()->delete();
|
$user->accessTokens()->delete();
|
||||||
$user->authCodes()->delete();
|
$user->authCodes()->delete();
|
||||||
|
|
||||||
|
|||||||
541
composer.lock
generated
541
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Billing": {
|
"Billing": {
|
||||||
"repository": "solidtime-io/extension-billing",
|
"repository": "solidtime-io/extension-billing",
|
||||||
"ref": "v0.0.7"
|
"ref": "v0.0.8"
|
||||||
},
|
},
|
||||||
"Services": {
|
"Services": {
|
||||||
"repository": "solidtime-io/extension-services",
|
"repository": "solidtime-io/extension-services",
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ export function useEventResize(params: {
|
|||||||
edge: 'start' | 'end',
|
edge: 'start' | 'end',
|
||||||
dayStr: string
|
dayStr: string
|
||||||
) {
|
) {
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
|||||||
@@ -168,6 +168,10 @@ defineExpose({ submit, focusAfterStart });
|
|||||||
data-testid="time_entry_description"
|
data-testid="time_entry_description"
|
||||||
class="w-full rounded-l-lg py-4 sm:py-2.5 px-3.5 border-b border-b-card-background-separator @2xl:px-4 text-base text-text-primary bg-transparent border-none placeholder-text-secondary focus:ring-0 transition"
|
class="w-full rounded-l-lg py-4 sm:py-2.5 px-3.5 border-b border-b-card-background-separator @2xl:px-4 text-base text-text-primary bg-transparent border-none placeholder-text-secondary focus:ring-0 transition"
|
||||||
type="text"
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
data-1p-ignore
|
||||||
|
data-lpignore="true"
|
||||||
|
data-form-type="other"
|
||||||
@keydown.enter="submit"
|
@keydown.enter="submit"
|
||||||
@keydown.esc="showDropdown = false"
|
@keydown.esc="showDropdown = false"
|
||||||
@blur="updateTimeEntryDescription" />
|
@blur="updateTimeEntryDescription" />
|
||||||
|
|||||||
@@ -170,6 +170,10 @@ function closeAndFocusInput() {
|
|||||||
: 'text-text-primary bg-card-background border-border-secondary border border-none'
|
: 'text-text-primary bg-card-background border-border-secondary border border-none'
|
||||||
"
|
"
|
||||||
type="text"
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
data-1p-ignore
|
||||||
|
data-lpignore="true"
|
||||||
|
data-form-type="other"
|
||||||
@focusin="openModalOnTab"
|
@focusin="openModalOnTab"
|
||||||
@click="openModalOnClick"
|
@click="openModalOnClick"
|
||||||
@keydown.exact.tab="focusNextElement"
|
@keydown.exact.tab="focusNextElement"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const useNotificationsStore = defineStore('notifications', () => {
|
|||||||
const showActionBlockedModal = ref(false);
|
const showActionBlockedModal = ref(false);
|
||||||
|
|
||||||
function addNotification(type: NotificationType, title: string, message?: string) {
|
function addNotification(type: NotificationType, title: string, message?: string) {
|
||||||
const uuid = crypto.randomUUID();
|
const uuid = Math.random().toString(36).substring(7);
|
||||||
notifications.value.push({ title, message, type, uuid });
|
notifications.value.push({ title, message, type, uuid });
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembe
|
|||||||
use App\Models\Client;
|
use App\Models\Client;
|
||||||
use App\Models\Member;
|
use App\Models\Member;
|
||||||
use App\Models\Organization;
|
use App\Models\Organization;
|
||||||
|
use App\Models\Passport\Client as PassportClient;
|
||||||
|
use App\Models\Passport\RefreshToken;
|
||||||
|
use App\Models\Passport\Token;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\ProjectMember;
|
use App\Models\ProjectMember;
|
||||||
use App\Models\Report;
|
use App\Models\Report;
|
||||||
@@ -23,6 +26,7 @@ use Illuminate\Support\Collection;
|
|||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use PHPUnit\Framework\Attributes\CoversClass;
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||||||
use Tests\TestCaseWithDatabase;
|
use Tests\TestCaseWithDatabase;
|
||||||
use TiMacDonald\Log\LogEntry;
|
use TiMacDonald\Log\LogEntry;
|
||||||
@@ -424,4 +428,45 @@ class DeletionServiceTest extends TestCaseWithDatabase
|
|||||||
'role' => Role::Placeholder->value,
|
'role' => Role::Placeholder->value,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_delete_user_deletes_access_tokens_and_their_refresh_tokens_but_does_not_delete_tokens_of_other_users(): void
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$otherUser = User::factory()->create();
|
||||||
|
$passportClient = PassportClient::factory()->create();
|
||||||
|
|
||||||
|
$userToken = Token::factory()->forUser($user)->forClient($passportClient)->create();
|
||||||
|
$userRefreshToken = RefreshToken::query()->create([
|
||||||
|
'id' => Str::random(100),
|
||||||
|
'access_token_id' => $userToken->getKey(),
|
||||||
|
'revoked' => false,
|
||||||
|
'expires_at' => now()->addDays(30),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$otherUserToken = Token::factory()->forUser($otherUser)->forClient($passportClient)->create();
|
||||||
|
$otherUserRefreshToken = RefreshToken::query()->create([
|
||||||
|
'id' => Str::random(100),
|
||||||
|
'access_token_id' => $otherUserToken->getKey(),
|
||||||
|
'revoked' => false,
|
||||||
|
'expires_at' => now()->addDays(30),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
$this->deletionService->deleteUser($user);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
$this->assertDatabaseMissing(Token::class, [
|
||||||
|
'id' => $userToken->getKey(),
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseMissing(RefreshToken::class, [
|
||||||
|
'id' => $userRefreshToken->getKey(),
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseHas(Token::class, [
|
||||||
|
'id' => $otherUserToken->getKey(),
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseHas(RefreshToken::class, [
|
||||||
|
'id' => $otherUserRefreshToken->getKey(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user