Adds the user name as a separate field
This commit is contained in:
@@ -91,31 +91,27 @@ private function processCallback(): RedirectResponse
|
||||
Log::info('[Azure SSO] Azure user resolved', [
|
||||
'azure_id' => $azureUser->getId(),
|
||||
'email' => $azureUser->getEmail(),
|
||||
'mail' => Arr::get($azureUser->user, 'mail'),
|
||||
'name' => $azureUser->getName(),
|
||||
'job_title' => Arr::get($azureUser->user, 'jobTitle'),
|
||||
'department' => Arr::get($azureUser->user, 'department'),
|
||||
'company' => Arr::get($azureUser->user, 'companyName'),
|
||||
]);
|
||||
|
||||
Log::info('[Azure SSO] Full Azure user dump', [
|
||||
'raw_user' => $azureUser->user,
|
||||
'token' => substr((string) $azureUser->token, 0, 12).'…',
|
||||
'refresh_token' => $azureUser->refreshToken ? 'present' : 'absent',
|
||||
'expires_in' => $azureUser->expiresIn,
|
||||
'avatar' => $azureUser->getAvatar(),
|
||||
'nickname' => $azureUser->getNickname(),
|
||||
]);
|
||||
Log::info('[Azure SSO] Full Azure user dump', json_decode(json_encode($azureUser), true));
|
||||
|
||||
$user = User::query()->updateOrCreate(
|
||||
['email' => $azureUser->getEmail()],
|
||||
['username' => $azureUser->getEmail()],
|
||||
[
|
||||
'name' => $azureUser->getName(),
|
||||
'email' => $azureUser->user['mail'] ?? $azureUser->getEmail(),
|
||||
'azure_id' => $azureUser->getId(),
|
||||
'photo' => $azureUser->getAvatar(),
|
||||
'job_title' => Arr::get($azureUser->user, 'jobTitle'),
|
||||
'department' => Arr::get($azureUser->user, 'department'),
|
||||
'company_name' => Arr::get($azureUser->user, 'companyName'),
|
||||
'phone' => Arr::get($azureUser->user, 'mobilePhone', Arr::get($azureUser->user, 'businessPhones.0')),
|
||||
'email_verified_at' => now(),
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ final class User extends Authenticatable
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'username',
|
||||
'email',
|
||||
'password',
|
||||
'azure_id',
|
||||
|
||||
@@ -36,7 +36,7 @@ final class User extends Resource
|
||||
* @var array
|
||||
*/
|
||||
public static $search = [
|
||||
'id', 'name', 'email', 'department', 'job_title',
|
||||
'id', 'name', 'username', 'email', 'department', 'job_title',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -59,12 +59,19 @@ public function fields(NovaRequest $request): array
|
||||
->rules('required', 'max:255')
|
||||
->help('The user\'s full name, imported from Azure AD when they first log in.'),
|
||||
|
||||
Text::make('Username')
|
||||
->sortable()
|
||||
->rules('required', 'max:255')
|
||||
->creationRules('unique:users,username')
|
||||
->updateRules('unique:users,username,{{resourceId}}')
|
||||
->help('The user\'s Azure AD principal name (UPN), used to identify them when logging in via SSO.'),
|
||||
|
||||
Text::make('Email')
|
||||
->sortable()
|
||||
->rules('required', 'email', 'max:254')
|
||||
->creationRules('unique:users,email')
|
||||
->updateRules('unique:users,email,{{resourceId}}')
|
||||
->help('The user\'s email address, used to identify them when logging in via Azure AD.'),
|
||||
->help('The user\'s email address.'),
|
||||
|
||||
Text::make('Azure ID', 'azure_id')
|
||||
->onlyOnDetail()
|
||||
|
||||
@@ -27,6 +27,7 @@ public function definition(): array
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'username' => fake()->unique()->userName(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => self::$password ??= Hash::make('password'),
|
||||
|
||||
@@ -17,6 +17,7 @@ public function up(): void
|
||||
$table->id();
|
||||
$table->foreignId('role_id')->default(1)->constrained();
|
||||
$table->string('name');
|
||||
$table->string('username')->unique();
|
||||
$table->string('email')->unique();
|
||||
$table->string('azure_id')->nullable()->unique();
|
||||
$table->string('photo')->nullable();
|
||||
|
||||
@@ -1,22 +1,32 @@
|
||||
# Database Schema Documentation
|
||||
|
||||
> Generated: 2026-02-03 05:38:33
|
||||
> Generated: 2026-03-19 10:55:06
|
||||
> Database: go-no-go
|
||||
> Total Tables: 13
|
||||
> Total Tables: 23
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [action_events](#action_events)
|
||||
- [answers](#answers)
|
||||
- [cache](#cache)
|
||||
- [cache_locks](#cache_locks)
|
||||
- [categories](#categories)
|
||||
- [configs](#configs)
|
||||
- [failed_jobs](#failed_jobs)
|
||||
- [job_batches](#job_batches)
|
||||
- [jobs](#jobs)
|
||||
- [logs](#logs)
|
||||
- [migrations](#migrations)
|
||||
- [nova_field_attachments](#nova_field_attachments)
|
||||
- [nova_notifications](#nova_notifications)
|
||||
- [nova_pending_field_attachments](#nova_pending_field_attachments)
|
||||
- [password_reset_tokens](#password_reset_tokens)
|
||||
- [question_groups](#question_groups)
|
||||
- [questionnaire_sessions](#questionnaire_sessions)
|
||||
- [questions](#questions)
|
||||
- [roles](#roles)
|
||||
- [screening_answers](#screening_answers)
|
||||
- [screenings](#screenings)
|
||||
- [sessions](#sessions)
|
||||
- [users](#users)
|
||||
|
||||
@@ -46,6 +56,25 @@ ## action_events
|
||||
|
||||
---
|
||||
|
||||
## answers
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| session_id | bigint unsigned | NO | MUL | NULL | | → questionnaire_sessions.id |
|
||||
| question_id | bigint unsigned | NO | MUL | NULL | | → questions.id |
|
||||
| value | varchar(255) | YES | | NULL | | |
|
||||
| text_value | text | YES | | NULL | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
### Foreign Key Constraints
|
||||
|
||||
- **answers_question_id_foreign**: `question_id` → `questions.id`
|
||||
- **answers_session_id_foreign**: `session_id` → `questionnaire_sessions.id`
|
||||
|
||||
---
|
||||
|
||||
## cache
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
@@ -66,6 +95,29 @@ ## cache_locks
|
||||
|
||||
---
|
||||
|
||||
## categories
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| name | varchar(255) | NO | UNI | NULL | | |
|
||||
| sort_order | int unsigned | NO | | 0 | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
---
|
||||
|
||||
## configs
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| key | varchar(255) | NO | PRI | NULL | | |
|
||||
| json_value | json | YES | | NULL | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
---
|
||||
|
||||
## failed_jobs
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
@@ -111,6 +163,26 @@ ## jobs
|
||||
|
||||
---
|
||||
|
||||
## logs
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| user_id | bigint unsigned | YES | MUL | NULL | | → users.id |
|
||||
| session_id | bigint unsigned | YES | MUL | NULL | | → questionnaire_sessions.id |
|
||||
| category_id | bigint unsigned | YES | MUL | NULL | | → categories.id |
|
||||
| action | varchar(100) | NO | | NULL | | |
|
||||
| metadata | json | YES | | NULL | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
|
||||
### Foreign Key Constraints
|
||||
|
||||
- **logs_category_id_foreign**: `category_id` → `categories.id`
|
||||
- **logs_session_id_foreign**: `session_id` → `questionnaire_sessions.id`
|
||||
- **logs_user_id_foreign**: `user_id` → `users.id`
|
||||
|
||||
---
|
||||
|
||||
## migrations
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
@@ -175,6 +247,116 @@ ## password_reset_tokens
|
||||
|
||||
---
|
||||
|
||||
## question_groups
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| category_id | bigint unsigned | NO | MUL | NULL | | → categories.id |
|
||||
| name | varchar(255) | NO | | NULL | | |
|
||||
| sort_order | int unsigned | NO | | 0 | | |
|
||||
| description | text | YES | | NULL | | |
|
||||
| scoring_instructions | text | YES | | NULL | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
### Foreign Key Constraints
|
||||
|
||||
- **question_groups_category_id_foreign**: `category_id` → `categories.id`
|
||||
|
||||
---
|
||||
|
||||
## questionnaire_sessions
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| user_id | bigint unsigned | NO | MUL | NULL | | → users.id |
|
||||
| category_id | bigint unsigned | NO | MUL | NULL | | → categories.id |
|
||||
| screening_id | bigint unsigned | YES | MUL | NULL | | → screenings.id |
|
||||
| status | varchar(50) | NO | | in_progress | | |
|
||||
| score | int | YES | | NULL | | |
|
||||
| result | varchar(50) | YES | | NULL | | |
|
||||
| additional_comments | text | YES | | NULL | | |
|
||||
| completed_at | timestamp | YES | | NULL | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
### Foreign Key Constraints
|
||||
|
||||
- **questionnaire_sessions_category_id_foreign**: `category_id` → `categories.id`
|
||||
- **questionnaire_sessions_screening_id_foreign**: `screening_id` → `screenings.id`
|
||||
- **questionnaire_sessions_user_id_foreign**: `user_id` → `users.id`
|
||||
|
||||
---
|
||||
|
||||
## questions
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| question_group_id | bigint unsigned | NO | MUL | NULL | | → question_groups.id |
|
||||
| text | text | NO | | NULL | | |
|
||||
| has_yes | tinyint(1) | NO | | 0 | | |
|
||||
| has_no | tinyint(1) | NO | | 0 | | |
|
||||
| has_na | tinyint(1) | NO | | 0 | | |
|
||||
| details | varchar(50) | YES | | NULL | | |
|
||||
| sort_order | int unsigned | NO | | 0 | | |
|
||||
| is_scored | tinyint(1) | NO | | 0 | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
### Foreign Key Constraints
|
||||
|
||||
- **questions_question_group_id_foreign**: `question_group_id` → `question_groups.id`
|
||||
|
||||
---
|
||||
|
||||
## roles
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| name | varchar(255) | NO | UNI | NULL | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
---
|
||||
|
||||
## screening_answers
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| screening_id | bigint unsigned | NO | MUL | NULL | | → screenings.id |
|
||||
| question_number | int unsigned | NO | | NULL | | |
|
||||
| value | varchar(10) | NO | | NULL | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
### Foreign Key Constraints
|
||||
|
||||
- **screening_answers_screening_id_foreign**: `screening_id` → `screenings.id`
|
||||
|
||||
---
|
||||
|
||||
## screenings
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| user_id | bigint unsigned | NO | MUL | NULL | | → users.id |
|
||||
| score | decimal(4,1) | YES | | NULL | | |
|
||||
| passed | tinyint(1) | YES | | NULL | | |
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
### Foreign Key Constraints
|
||||
|
||||
- **screenings_user_id_foreign**: `user_id` → `users.id`
|
||||
|
||||
---
|
||||
|
||||
## sessions
|
||||
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
@@ -193,10 +375,18 @@ ## users
|
||||
| Field | Type | Null | Key | Default | Extra | Foreign Key |
|
||||
|-------|------|------|-----|---------|-------|-------------|
|
||||
| id | bigint unsigned | NO | PRI | NULL | auto_increment | |
|
||||
| role_id | bigint unsigned | NO | MUL | 1 | | → roles.id |
|
||||
| name | varchar(255) | NO | | NULL | | |
|
||||
| username | varchar(255) | NO | UNI | NULL | | |
|
||||
| email | varchar(255) | NO | UNI | NULL | | |
|
||||
| azure_id | varchar(255) | YES | UNI | NULL | | |
|
||||
| photo | varchar(255) | YES | | NULL | | |
|
||||
| job_title | varchar(255) | YES | | NULL | | |
|
||||
| department | varchar(255) | YES | | NULL | | |
|
||||
| company_name | varchar(255) | YES | | NULL | | |
|
||||
| phone | varchar(255) | YES | | NULL | | |
|
||||
| email_verified_at | timestamp | YES | | NULL | | |
|
||||
| password | varchar(255) | NO | | NULL | | |
|
||||
| password | varchar(255) | YES | | NULL | | |
|
||||
| two_factor_secret | text | YES | | NULL | | |
|
||||
| two_factor_recovery_codes | text | YES | | NULL | | |
|
||||
| two_factor_confirmed_at | timestamp | YES | | NULL | | |
|
||||
@@ -204,5 +394,9 @@ ## users
|
||||
| created_at | timestamp | YES | | NULL | | |
|
||||
| updated_at | timestamp | YES | | NULL | | |
|
||||
|
||||
### Foreign Key Constraints
|
||||
|
||||
- **users_role_id_foreign**: `role_id` → `roles.id`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ public function run(): void
|
||||
|
||||
DB::table('users')->insert([
|
||||
'name' => 'Jonathan',
|
||||
'username' => 'jonathan.van.rij@agerion.nl',
|
||||
'email' => 'jonathan.van.rij@agerion.nl',
|
||||
'password' => bcrypt('secret'),
|
||||
'email_verified_at' => now(),
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
// Dev auto-login route
|
||||
Route::get('/login-for-testing', function () {
|
||||
$user = \App\Models\User::where('email', 'jonathan.van.rij@agerion.nl')->first();
|
||||
$user = \App\Models\User::where('username', 'jonathan.van.rij@agerion.nl')->first();
|
||||
|
||||
auth()->login($user);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
use Laravel\Socialite\Two\User as SocialiteUser;
|
||||
use Mockery;
|
||||
@@ -31,13 +32,18 @@ public function test_callback_creates_new_user_and_logs_in(): void
|
||||
$this->markTestSkipped('Skipped due to application bug: password field is NOT NULL but controller passes null');
|
||||
}
|
||||
|
||||
public function test_callback_matches_existing_user_by_email(): void
|
||||
public function test_callback_matches_existing_user_by_username(): void
|
||||
{
|
||||
$existingUser = User::factory()->create([
|
||||
'email' => 'existing@example.com',
|
||||
DB::table('users')->insert([
|
||||
'username' => 'existing@example.com',
|
||||
'email' => 'real@example.com',
|
||||
'name' => 'Original Name',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$existingUser = User::where('username', 'existing@example.com')->first();
|
||||
|
||||
$socialiteUser = Mockery::mock(SocialiteUser::class);
|
||||
$socialiteUser->shouldReceive('getEmail')->andReturn('existing@example.com');
|
||||
$socialiteUser->shouldReceive('getName')->andReturn('Updated Name');
|
||||
@@ -45,6 +51,7 @@ public function test_callback_matches_existing_user_by_email(): void
|
||||
$socialiteUser->shouldReceive('getAvatar')->andReturn(null);
|
||||
$socialiteUser->shouldReceive('offsetExists')->andReturn(false);
|
||||
$socialiteUser->user = [
|
||||
'mail' => 'real@example.com',
|
||||
'jobTitle' => null,
|
||||
'department' => null,
|
||||
'companyName' => null,
|
||||
@@ -62,11 +69,12 @@ public function test_callback_matches_existing_user_by_email(): void
|
||||
$this->get('/auth/callback')
|
||||
->assertRedirect('/');
|
||||
|
||||
$this->assertEquals(1, User::where('email', 'existing@example.com')->count());
|
||||
$this->assertEquals(1, User::where('username', 'existing@example.com')->count());
|
||||
|
||||
$existingUser->refresh();
|
||||
|
||||
$this->assertEquals('Updated Name', $existingUser->name);
|
||||
$this->assertEquals('real@example.com', $existingUser->email);
|
||||
$this->assertAuthenticatedAs($existingUser);
|
||||
}
|
||||
|
||||
@@ -82,15 +90,18 @@ public function test_logout_logs_out_and_redirects_to_landing(): void
|
||||
|
||||
public function test_login_jonathan_works_in_testing_env(): void
|
||||
{
|
||||
User::factory()->create([
|
||||
DB::table('users')->insert([
|
||||
'username' => 'jonathan.van.rij@agerion.nl',
|
||||
'email' => 'jonathan@blijnder.nl',
|
||||
'name' => 'Jonathan',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->get('/login-for-testing')
|
||||
->assertRedirect('/');
|
||||
|
||||
$user = User::where('email', 'jonathan@blijnder.nl')->first();
|
||||
$user = User::where('username', 'jonathan.van.rij@agerion.nl')->first();
|
||||
|
||||
$this->assertAuthenticatedAs($user);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user