Compare commits

...

7 Commits

Author SHA1 Message Date
Bubka 5a74a37348 Add tests for the Authentication log feature 2024-04-16 20:38:05 +02:00
Bubka ec396b00e8 Change log position & Small adjustments & Hide full log link 2024-04-16 16:15:45 +02:00
Bubka ab3240ec53 Remove useless css class 2024-04-16 15:28:52 +02:00
Bubka e5f6fbf431 Fix auth log querying 2024-04-16 15:28:38 +02:00
Bubka a12a03d330 Fix typo in translations 2024-04-16 15:27:18 +02:00
Bubka 6e41e284b5 Fix light theme 2024-04-16 11:02:38 +02:00
Bubka 0e73738ee1 Add Close buttons to ease navigation 2024-04-16 09:22:38 +02:00
13 changed files with 517 additions and 55 deletions

View File

@ -220,7 +220,7 @@ class UserManagerController extends Controller
'limit' => 'sometimes|numeric',
]);
$authentications = $request->has('period') ? $user->authentications($validated['period'])->get() : $user->authentications->get();
$authentications = $request->has('period') ? $user->authenticationsByPeriod($validated['period']) : $user->authentications;
$authentications = $request->has('limit') ? $authentications->take($validated['limit']) : $authentications;
return UserAuthentication::collection($authentications);

View File

@ -2,6 +2,7 @@
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Rappasoft\LaravelAuthenticationLog\Models\AuthenticationLog;
use Rappasoft\LaravelAuthenticationLog\Traits\AuthenticationLoggable as TraitsAuthenticationLoggable;
@ -10,13 +11,20 @@ trait AuthenticationLoggable
{
use TraitsAuthenticationLoggable;
public function authentications(int $period = 1)
public function authentications()
{
return $this->morphMany(AuthenticationLog::class, 'authenticatable')->latest('id');
}
/**
* Get authentications for the provided timespan (in month)
*/
public function authenticationsByPeriod(int $period = 1)
{
$from = Carbon::now()->subMonths($period);
return $this->morphMany(AuthenticationLog::class, 'authenticatable')
->where('login_at', '>=', $from)
->orWhere('logout_at', '>=', $from)
->orderByDesc('id');
return $this->authentications->filter(function (AuthenticationLog $authentication) use ($from) {
return $authentication->login_at >= $from || $authentication->logout_at >= $from;
});
}
}

View File

@ -1390,4 +1390,15 @@ footer.main .field.is-grouped {
top: 4px;
width: 29px;
height: 29px;
}
.light-or-darker {
color: $grey-darker;
}
:root[data-theme="dark"] .light-or-darker {
color: $grey-light;
}
.width-1-5x {
width: 1.5em;
}

View File

@ -4,6 +4,7 @@
import Spinner from '@/components/Spinner.vue'
import { useNotifyStore } from '@/stores/notify'
import { FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import { UseColorMode } from '@vueuse/components'
const notify = useNotifyStore()
@ -26,6 +27,8 @@
const period = ref(periods.aMonth)
const orderIsDesc = ref(true)
const emit = defineEmits(['has-more-entries'])
const visibleAuthentications = computed(() => {
return authentications.value.filter(authentication => {
return JSON.stringify(authentication)
@ -84,11 +87,17 @@
*/
function getAuthentications() {
isFetching.value = true
let limit = props.lastOnly ? 3 : false
let limit = props.lastOnly ? 4 : false
userService.getauthentications(props.userId, period.value, limit, {returnError: true})
.then(response => {
authentications.value = response.data
if (authentications.value.length > 3) {
emit('has-more-entries')
authentications.value.pop()
}
orderIsDesc.value == true ? sortDesc() : sortAsc()
})
.catch(error => {
@ -153,26 +162,28 @@
</nav>
<div v-if="visibleAuthentications.length > 0">
<div v-for="authentication in visibleAuthentications" :key="authentication.id" class="list-item is-size-6 is-size-7-mobile has-text-grey is-flex is-justify-content-space-between">
<div>
<UseColorMode v-slot="{ mode }">
<div>
<span v-if="isFailedEntry(authentication)" v-html="$t('admin.failed_login_on', { login_at: authentication.login_at })" />
<span v-else-if="isSuccessfulLogout(authentication)" v-html="$t('admin.successful_logout_on', { login_at: authentication.logout_at })" />
<span v-else v-html="$t('admin.successful_login_on', { login_at: authentication.login_at })" />
<div>
<span v-if="isFailedEntry(authentication)" v-html="$t('admin.failed_login_on', { login_at: authentication.login_at })" />
<span v-else-if="isSuccessfulLogout(authentication)" v-html="$t('admin.successful_logout_on', { login_at: authentication.logout_at })" />
<span v-else v-html="$t('admin.successful_login_on', { login_at: authentication.login_at })" />
</div>
<div>
{{ $t('commons.IP') }}: <span class="light-or-darker">{{ authentication.ip_address }}</span> -
{{ $t('commons.browser') }}: <span class="light-or-darker">{{ authentication.browser }}</span> -
{{ $t('commons.operating_system_short') }}: <span class="light-or-darker">{{ authentication.platform }}</span>
</div>
</div>
<div>
{{ $t('commons.IP') }}: <span class="has-text-grey-light">{{ authentication.ip_address }}</span> -
{{ $t('commons.browser') }}: <span class="has-text-grey-light">{{ authentication.browser }}</span> -
{{ $t('commons.operating_system_short') }}: <span class="has-text-grey-light">{{ authentication.platform }}</span>
<div :class="mode == 'dark' ? 'has-text-grey-darker' : 'has-text-grey-lighter'" class="is-align-self-center ">
<font-awesome-layers class="fa-2x width-1-5x">
<FontAwesomeIcon :icon="['fas', deviceIcon(authentication.device)]" transform="grow-6" fixed-width />
<FontAwesomeIcon :icon="['fas', isFailedEntry(authentication) ? 'times' : 'check']"
:transform="'shrink-7' + (authentication.device == 'desktop' ? ' up-2' : '')"
:class="isFailedEntry(authentication) ? 'has-text-danger' + (mode == 'dark' ? '-dark' : '') : 'has-text-success' + (mode == 'dark' ? '-dark' : '')" fixed-width />
</font-awesome-layers>
</div>
</div>
<div class="is-align-self-center has-text-grey-darker">
<font-awesome-layers class="fa-2x">
<FontAwesomeIcon :icon="['fas', deviceIcon(authentication.device)]" transform="grow-6" fixed-width />
<FontAwesomeIcon :icon="['fas', isFailedEntry(authentication) ? 'times' : 'check']"
:transform="'shrink-7' + (authentication.device == 'desktop' ? ' up-2' : '')"
:class="isFailedEntry(authentication) ? 'has-text-danger-dark' : 'has-text-success-dark'" fixed-width />
</font-awesome-layers>
</div>
</UseColorMode>
</div>
</div>
<div v-else-if="authentications.length == 0" class="mt-4">

View File

@ -1,7 +1,6 @@
<script setup>
const { copy } = useClipboard({ legacy: true })
import { useNotifyStore } from '@/stores/notify'
import { UseColorMode } from '@vueuse/components'
const notify = useNotifyStore()
@ -48,9 +47,7 @@
<div class="block">
{{ $t('errors.data_of_qrcode_is_not_valid_URI') }}
</div>
<UseColorMode v-slot="{ mode }">
<div class="block mb-6" :class="mode == 'dark' ? 'has-text-light':'has-text-grey-dark'">{{ qrContent ? qrContent : '[' + trans('commons.nothing') + ']' }}</div>
</UseColorMode>
<div class="block mb-6 light-or-darker">{{ qrContent ? qrContent : '[' + trans('commons.nothing') + ']' }}</div>
<!-- Copy to clipboard -->
<div class="block has-text-link" v-if="qrContent">
<button class="button is-link is-outlined is-rounded" @click.stop="copyToClipboard(qrContent)">

View File

@ -3,7 +3,6 @@
import { useUserStore } from '@/stores/user'
import { useBusStore } from '@/stores/bus'
import { useNotifyStore } from '@/stores/notify'
import { UseColorMode } from '@vueuse/components'
import { useTwofaccounts } from '@/stores/twofaccounts'
const router = useRouter()
@ -85,9 +84,7 @@
</div>
<!-- alternative methods -->
<div class="column is-full">
<UseColorMode v-slot="{ mode }">
<div class="block" :class="mode == 'dark' ? 'has-text-light':'has-text-grey-dark'">{{ $t('twofaccounts.forms.alternative_methods') }}</div>
</UseColorMode>
<div class="block light-or-darker">{{ $t('twofaccounts.forms.alternative_methods') }}</div>
<!-- upload a qr code -->
<div class="block has-text-link" v-if="!user.preferences.useBasicQrcodeReader">
<label role="button" tabindex="0" class="button is-link is-outlined is-rounded" ref="qrcodeInputLabel" @keyup.enter="qrcodeInputLabel.click()">

View File

@ -165,7 +165,7 @@
<div class="ml-3">
<UseColorMode v-slot="{ mode }">
<!-- manage link -->
<RouterLink :to="{ name: 'admin.manageUser', params: { userId: user.id }}" class="button is-small has-normal-radius is-pulled-right" :class="{'is-dark' : mode == 'dark'}" :title="$t('commons.manage')">
<RouterLink :to="{ name: 'admin.manageUser', params: { userId: user.id }}" class="button is-small has-normal-radius" :class="{'is-dark' : mode == 'dark'}" :title="$t('commons.manage')">
{{ $t('commons.manage') }}
</RouterLink>
</UseColorMode>

View File

@ -40,7 +40,8 @@
<AccessLogViewer :userId="props.userId" :lastOnly="false" :showSearch="true" />
<!-- footer -->
<VueFooter :showButtons="true">
<ButtonBackCloseCancel :returnTo="{ name: 'admin.manageUser', params: { userId: props.userId }}" action="close" />
<ButtonBackCloseCancel :returnTo="{ name: 'admin.manageUser', params: { userId: props.userId }}" action="back" />
<ButtonBackCloseCancel :returnTo="{ name: 'accounts' }" action="close" />
</VueFooter>
</ResponsiveWidthWrapper>
</template>

View File

@ -15,6 +15,7 @@
const isFetching = ref(false)
const managedUser = ref(null)
const listUserPreferences = ref(null)
const showFullLogLink = ref(false)
const props = defineProps({
userId: [Number, String]
@ -208,6 +209,9 @@
<div v-if="managedUser.info.oauth_provider" class="notification is-dark is-size-7-mobile has-text-centered">
{{ $t('admin.account_bound_to_x_via_oauth', { provider: managedUser.info.oauth_provider }) }}
</div>
<div class="block is-size-6 is-size-7-mobile has-text-grey">
{{ $t('admin.registered_on_date', { date: managedUser.info.created_at }) }} - {{ $t('admin.last_seen_on_date', { date: managedUser.info.last_seen_at }) }}
</div>
<div class="block">
<!-- otp as dot -->
<FormCheckbox v-model="managedUser.info.is_admin" @update:model-value="val => saveAdminRole(val === true)" fieldName="is_admin" label="admin.forms.is_admin.label" help="admin.forms.is_admin.help" />
@ -244,7 +248,7 @@
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
<div>
<span class="has-text-weight-bold">{{ $t('settings.personal_access_tokens') }}</span>
<span class="is-block is-family-primary is-size-7 is-size-7-mobile has-text-grey-dark">
<span class="is-block is-family-primary has-text-grey-dark">
{{ $t('admin.user_has_x_active_pat', { count: managedUser.valid_personal_access_tokens }) }}
</span>
</div>
@ -262,7 +266,7 @@
<div class="list-item is-size-6 is-size-6-mobile has-text-grey is-flex is-justify-content-space-between">
<div>
<span class="has-text-weight-bold">{{ $t('auth.webauthn.security_devices') }}</span>
<span class="is-block is-size-7 is-size-7-mobile has-text-grey-dark">
<span class="is-block has-text-grey-dark">
{{ $t('admin.user_has_x_security_devices', { count: managedUser.webauthn_credentials }) }}
</span>
</div>
@ -278,6 +282,16 @@
</div>
</div>
</div>
<div class="block">
<h3 class="title is-5 has-text-grey-light mb-2">{{ $t('admin.last_accesses') }}</h3>
<AccessLogViewer :userId="props.userId" :lastOnly="true" @has-more-entries="showFullLogLink = true"/>
</div>
<div v-if="showFullLogLink" class="block is-size-6 is-size-7-mobile has-text-grey">
{{ $t('admin.access_log_has_more_entries') }} <router-link id="lnkFullLogs" :to="{ name: 'admin.logs.access', params: { userId: props.userId }}" >
{{ $t('admin.see_full_log') }}.
</router-link>
</div>
<!-- preferences -->
<h2 class="title is-4 has-text-grey-light">{{ $t('settings.preferences') }}</h2>
<div class="about-debug box is-family-monospace is-size-7">
<CopyButton id="btnCopyEnvVars" :token="listUserPreferences?.innerText" />
@ -287,20 +301,6 @@
</li>
</ul>
</div>
<!-- logs -->
<h2 class="title is-4 has-text-grey-light">{{ $t('admin.logs') }}</h2>
<div class="block is-size-6 is-size-7-mobile has-text-grey">
{{ $t('admin.registered_on_date', { date: managedUser.info.created_at }) }} - {{ $t('admin.last_seen_on_date', { date: managedUser.info.last_seen_at }) }}
</div>
<div class="block">
<h3 class="title is-6 has-text-grey-light mb-0">{{ $t('admin.last_accesses') }}</h3>
<AccessLogViewer :userId="props.userId" :lastOnly="true" />
</div>
<div class="block is-size-6 is-size-7-mobile has-text-grey">
{{ $t('admin.access_log_has_more_entries') }} <router-link id="lnkFullLogs" :to="{ name: 'admin.logs.access', params: { userId: props.userId }}" >
{{ $t('admin.see_full_log') }}
</router-link>
</div>
<!-- danger zone -->
<h2 class="title is-4 has-text-danger">{{ $t('admin.danger_zone') }}</h2>
<div class="is-left-bordered-danger">
@ -318,6 +318,7 @@
<!-- footer -->
<VueFooter :showButtons="true">
<ButtonBackCloseCancel :returnTo="{ name: 'admin.users' }" action="back" />
<ButtonBackCloseCancel :returnTo="{ name: 'accounts' }" action="close" />
</VueFooter>
</ResponsiveWidthWrapper>
</template>

View File

@ -68,13 +68,13 @@ return [
'check_now' => 'Check now',
'view_on_github' => 'View on Github',
'x_is_available' => ':version is available',
'successful_login_on' => 'Successful login on <span class="has-text-grey-light">:login_at</span>',
'successful_logout_on' => 'Successful logout on <span class="has-text-grey-light">:login_at</span>',
'failed_login_on' => 'Failed login on <span class="has-text-grey-light">:login_at</span>',
'successful_login_on' => 'Successful login on <span class="light-or-darker">:login_at</span>',
'successful_logout_on' => 'Successful logout on <span class="light-or-darker">:login_at</span>',
'failed_login_on' => 'Failed login on <span class="light-or-darker">:login_at</span>',
'last_accesses' => 'Last accesses',
'see_full_log' => 'See full log',
'browser_on_platform' => ':browser on :platform',
'access_log_has_more_entries' => 'The access log is likely to contain more entries.',
'access_log_has_more_entries' => 'The access log contains more entries.',
'access_log_legend_for_user' => 'Full access log for user :username',
'show_last_month_log' => 'Show entries from the last month',
'show_three_months_log' => 'Show entries from the last 3 months',

View File

@ -90,5 +90,5 @@ return [
'device' => 'Device',
'one_month' => '1 mo.',
'x_month' => ':x mos.',
'one_year' => '1 yr',
'one_year' => '1 yr.',
];

View File

@ -10,6 +10,7 @@ use Database\Factories\UserFactory;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
@ -19,6 +20,8 @@ use Illuminate\Support\Str;
use Laravel\Passport\TokenRepository;
use Mockery\MockInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\Data\AuthenticationLogData;
use Tests\FeatureTestCase;
#[CoversClass(UserManagerController::class)]
@ -518,4 +521,278 @@ class UserManagerControllerTest extends FeatureTestCase
])
->assertForbidden();
}
protected function feedAuthenticationLog() : int
{
// Do not change creation order
$this->user->authentications()->create(AuthenticationLogData::beforeLastYear());
$this->user->authentications()->create(AuthenticationLogData::duringLastYear());
$this->user->authentications()->create(AuthenticationLogData::duringLastSixMonth());
$this->user->authentications()->create(AuthenticationLogData::duringLastThreeMonth());
$this->user->authentications()->create(AuthenticationLogData::duringLastMonth());
$this->user->authentications()->create(AuthenticationLogData::noLogin());
$this->user->authentications()->create(AuthenticationLogData::noLogout());
return 7;
}
/**
* @test
*/
public function test_authentications_returns_all_entries() : void
{
$created = $this->feedAuthenticationLog();
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
->assertOk()
->assertJsonCount($created);
}
/**
* @test
*/
public function test_authentications_returns_expected_resource() : void
{
$this->user->authentications()->create(AuthenticationLogData::duringLastMonth());
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications')
->assertJsonStructure([
'*' => [
'id',
'ip_address',
'user_agent',
'browser',
'platform',
'device',
'login_at',
'logout_at',
'login_successful',
'duration',
]
]);
}
/**
* @test
*/
public function test_authentications_returns_no_login_entry() : void
{
$this->user->authentications()->create(AuthenticationLogData::noLogin());
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
->assertJsonCount(1)
->assertJsonFragment([
'login_at' => null
]);
}
/**
* @test
*/
public function test_authentications_returns_no_logout_entry() : void
{
$this->user->authentications()->create(AuthenticationLogData::noLogout());
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
->assertJsonCount(1)
->assertJsonFragment([
'logout_at' => null
]);
}
/**
* @test
*/
public function test_authentications_returns_failed_entry() : void
{
$this->user->authentications()->create(AuthenticationLogData::failedLogin());
$expected = Carbon::parse(AuthenticationLogData::failedLogin()['login_at'])->toDayDateTimeString();
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
->assertJsonCount(1)
->assertJsonFragment([
'login_at' => $expected,
'login_successful' => false,
]);
}
/**
* @test
*/
public function test_authentications_returns_last_month_entries() : void
{
$this->feedAuthenticationLog();
$expected = Carbon::parse(AuthenticationLogData::duringLastMonth()['login_at'])->toDayDateTimeString();
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
->assertJsonCount(3)
->assertJsonFragment([
'login_at' => $expected
]);
}
/**
* @test
*/
public function test_authentications_returns_last_three_months_entries() : void
{
$this->feedAuthenticationLog();
$expectedOneMonth = Carbon::parse(AuthenticationLogData::duringLastMonth()['login_at'])->toDayDateTimeString();
$expectedThreeMonth = Carbon::parse(AuthenticationLogData::duringLastThreeMonth()['login_at'])->toDayDateTimeString();
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=3')
->assertJsonCount(4)
->assertJsonFragment([
'login_at' => $expectedOneMonth
])
->assertJsonFragment([
'login_at' => $expectedThreeMonth
]);
}
/**
* @test
*/
public function test_authentications_returns_last_six_months_entries() : void
{
$this->feedAuthenticationLog();
$expectedOneMonth = Carbon::parse(AuthenticationLogData::duringLastMonth()['login_at'])->toDayDateTimeString();
$expectedThreeMonth = Carbon::parse(AuthenticationLogData::duringLastThreeMonth()['login_at'])->toDayDateTimeString();
$expectedSixMonth = Carbon::parse(AuthenticationLogData::duringLastSixMonth()['login_at'])->toDayDateTimeString();
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=6')
->assertJsonCount(5)
->assertJsonFragment([
'login_at' => $expectedOneMonth
])
->assertJsonFragment([
'login_at' => $expectedThreeMonth
])
->assertJsonFragment([
'login_at' => $expectedSixMonth
]);
}
/**
* @test
*/
public function test_authentications_returns_last_year_entries() : void
{
$this->feedAuthenticationLog();
$expectedOneMonth = Carbon::parse(AuthenticationLogData::duringLastMonth()['login_at'])->toDayDateTimeString();
$expectedThreeMonth = Carbon::parse(AuthenticationLogData::duringLastThreeMonth()['login_at'])->toDayDateTimeString();
$expectedSixMonth = Carbon::parse(AuthenticationLogData::duringLastSixMonth()['login_at'])->toDayDateTimeString();
$expectedYear = Carbon::parse(AuthenticationLogData::duringLastYear()['login_at'])->toDayDateTimeString();
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=12')
->assertJsonCount(6)
->assertJsonFragment([
'login_at' => $expectedOneMonth
])
->assertJsonFragment([
'login_at' => $expectedThreeMonth
])
->assertJsonFragment([
'login_at' => $expectedSixMonth
])
->assertJsonFragment([
'login_at' => $expectedYear
]);
}
/**
* @test
*/
#[DataProvider('LimitProvider')]
public function test_authentications_returns_limited_entries($limit) : void
{
$this->feedAuthenticationLog();
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?limit=' . $limit)
->assertOk()
->assertJsonCount($limit);
}
/**
* Provide various limit
*/
public static function LimitProvider()
{
return [
'limited to 1' => [1],
'limited to 2' => [2],
'limited to 3' => [3],
];
}
/**
* @test
*/
public function test_authentications_returns_expected_ip_and_useragent_chunks() : void
{
$this->user->authentications()->create([
'ip_address' => '127.0.0.1',
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
'login_at' => now(),
'login_successful' => true,
'logout_at' => null,
'location' => null,
]);
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=1')
->assertJsonFragment([
'ip_address' => '127.0.0.1',
'browser' => 'Firefox',
'platform' => 'Windows',
'device' => 'desktop',
]);
}
/**
* @test
*/
#[DataProvider('invalidQueryParameterProvider')]
public function test_authentications_with_invalid_limit_returns_validation_error($limit) : void
{
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?limit=' . $limit)
->assertStatus(422);
}
/**
* @test
*/
#[DataProvider('invalidQueryParameterProvider')]
public function test_authentications_with_invalid_period_returns_validation_error($period) : void
{
$this->actingAs($this->admin, 'api-guard')
->json('GET', '/api/v1/users/' . $this->user->id . '/authentications?period=' . $period)
->assertStatus(422);
}
/**
* Provide various invalid value to test query parameter
*/
public static function invalidQueryParameterProvider()
{
return [
'empty' => [''],
'null' => ['null'],
'boolean' => ['true'],
'string' => ['string'],
'array' => ['[]'],
];
}
}

View File

@ -0,0 +1,159 @@
<?php
namespace Tests\Data;
class AuthenticationLogData
{
/**
* Indicate that the model should have login date.
*
* @return array
*/
public static function failedLogin()
{
$loginDate = now()->subDays(15);
return [
'ip_address' => fake()->ipv4(),
'user_agent' => fake()->userAgent(),
'login_at' => $loginDate,
'login_successful' => false,
'logout_at' => null,
'location' => null,
];
}
/**
* Indicate that the model should have no login date
*
* @return array
*/
public static function noLogin()
{
return [
'ip_address' => fake()->ipv4(),
'user_agent' => fake()->userAgent(),
'login_at' => null,
'login_successful' => false,
'logout_at' => now(),
'location' => null,
];
}
/**
* Indicate that the model should have no logout date
*
* @return array
*/
public static function noLogout()
{
return [
'ip_address' => fake()->ipv4(),
'user_agent' => fake()->userAgent(),
'login_at' => now(),
'login_successful' => true,
'logout_at' => null,
'location' => null,
];
}
/**
* Indicate that the model should have login during last month
*
* @return array
*/
public static function duringLastMonth()
{
$loginDate = now()->subDays(15);
$logoutDate = $loginDate->addHours(1);
return [
'ip_address' => '127.0.0.1',
'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0',
'login_at' => $loginDate,
'login_successful' => true,
'logout_at' => $logoutDate,
'location' => null,
];
}
/**
* Indicate that the model should have login during last 3 month
*
* @return array
*/
public static function duringLastThreeMonth()
{
$loginDate = now()->subMonths(2);
$logoutDate = $loginDate->addHours(1);
return [
'ip_address' => fake()->ipv4(),
'user_agent' => fake()->userAgent(),
'login_at' => $loginDate,
'login_successful' => true,
'logout_at' => $logoutDate,
'location' => null,
];
}
/**
* Indicate that the model should have login during last 6 month
*
* @return array
*/
public static function duringLastSixMonth()
{
$loginDate = now()->subMonths(4);
$logoutDate = $loginDate->addHours(1);
return [
'ip_address' => fake()->ipv4(),
'user_agent' => fake()->userAgent(),
'login_at' => $loginDate,
'login_successful' => true,
'logout_at' => $logoutDate,
'location' => null,
];
}
/**
* Indicate that the model should have login during last month
*
* @return array
*/
public static function duringLastYear()
{
$loginDate = now()->subMonths(10);
$logoutDate = $loginDate->addHours(1);
return [
'ip_address' => fake()->ipv4(),
'user_agent' => fake()->userAgent(),
'login_at' => $loginDate,
'login_successful' => true,
'logout_at' => $logoutDate,
'location' => null,
];
}
/**
* Indicate that the model should have login during last month
*
* @return array
*/
public static function beforeLastYear()
{
$loginDate = now()->subYears(2);
$logoutDate = $loginDate->addHours(1);
return [
'ip_address' => fake()->ipv4(),
'user_agent' => fake()->userAgent(),
'login_at' => $loginDate,
'login_successful' => true,
'logout_at' => $logoutDate,
'location' => null,
];
}
}