Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/Phaseolies/Auth/Authable.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,19 @@ public function update(array $attributes): bool
if ($this->hooksSuppressed) {
$name = $this->getAuthPasswordName();

$this->recordPasswordChange(
if (
isset($this->attributes[$this->primaryKey])
&& array_key_exists($name, $attributes)
&& in_array($name, $this->creatable, true)
&& $attributes[$name] !== ($this->getOriginalAttributes()[$name] ?? null)
? $attributes[$name]
: null
);
) {
// Normalise the value exactly as setAttribute() will, so the
// recorded hash is the one that ends up in the database.
$value = $this->castForSet($name, $this->sanitize($attributes[$name]));

$this->recordPasswordChange(
$value !== ($this->getOriginalAttributes()[$name] ?? $this->attributes[$name] ?? null) ? $value : null
);
}
}

return parent::update($attributes);
Expand Down
2 changes: 1 addition & 1 deletion src/Phaseolies/Auth/Security/Authenticate.php
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ public function refreshSessionFingerprint(): void
}

/**
* The other devices the user is signed in on, most recently active first.
* The devices the user is signed in on, this one included, most recently active first.
*
* @return array<int, array{ip_address: string|null, user_agent: string|null, last_activity: int, is_current: bool}>
*/
Expand Down
19 changes: 16 additions & 3 deletions src/Phaseolies/Console/Commands/SessionTableCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class SessionTableCommand extends Command
*
* @var string
*/
protected $name = 'session:table';
protected $name = 'session:table {--uuid}';

/**
* The description of the console command.
Expand Down Expand Up @@ -60,8 +60,8 @@ public function handle(): int
$stub = (string) file_get_contents(__DIR__ . '/stubs/session/create_sessions_table.stub');

file_put_contents($file, str_replace(
['{{ schema }}', '{{ table }}'],
[$this->schemaAccessor($connection), $table],
['{{ schema }}', '{{ table }}', '{{ user_id }}'],
[$this->schemaAccessor($connection), $table, $this->userIdColumn((bool) $this->option('uuid'))],
$stub
));

Expand Down Expand Up @@ -89,6 +89,19 @@ protected function schemaAccessor(?string $connection): string
: 'Schema::connection(' . var_export($connection, true) . ')->';
}

/**
* The user_id column definition, matching the type of the user's primary key.
*
* @param bool $uuid
* @return string
*/
protected function userIdColumn(bool $uuid): string
{
return $uuid
? "\$table->string('user_id', 36)->nullable()->index();"
: "\$table->unsignedBigInteger('user_id')->nullable()->index();";
}

/**
* Path of the application's migrations.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ return new class extends Migration
{
{{ schema }}create('{{ table }}', function (Blueprint $table) {
$table->string('id', 128)->primary();
// For UUID or ULID user keys, use: $table->string('user_id', 36)->nullable()->index();
$table->unsignedBigInteger('user_id')->nullable()->index();
{{ user_id }}
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
Expand Down
15 changes: 15 additions & 0 deletions tests/Auth/LogoutOtherDevicesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,21 @@ public function test_a_password_saved_without_hooks_keeps_a_parallel_request_sig
$this->assertTrue($this->isCurrentAndSignedIn($laptop));
}

public function test_a_password_updated_without_hooks_is_recorded_as_the_model_stores_it(): void
{
$laptop = $this->signIn('Laptop');
$hash = password_hash('a new password', PASSWORD_BCRYPT, ['cost' => 4]);

[$laptop] = $this->onDevice($laptop, function (Authenticate $auth) use ($hash): void {
// The model trims the value before saving it, so the hash recorded
// as pending must be the trimmed one, not the value passed in.
$auth->user()->withoutHook()->update(['password' => " {$hash} "]);
});

$this->assertSame($hash, $this->storedPassword());
$this->assertTrue($this->isCurrentAndSignedIn($laptop));
}

public function test_changing_another_users_password_leaves_this_session_alone(): void
{
Database::getPdoInstance(self::CONNECTION)
Expand Down
27 changes: 25 additions & 2 deletions tests/Console/SessionTableCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,29 @@ public function test_it_uses_the_default_connection_when_none_is_configured(): v
$this->assertStringNotContainsString('Schema::connection', $migration);
}

public function test_it_uses_an_integer_user_id_by_default(): void
{
$this->run_();

$migration = (string) file_get_contents(glob($this->dir . '/*_create_sessions_table.php')[0]);

$this->assertStringContainsString("\$table->unsignedBigInteger('user_id')->nullable()->index();", $migration);
$this->assertStringNotContainsString("\$table->string('user_id'", $migration);
$this->assertStringNotContainsString('{{ user_id }}', $migration);
}

public function test_the_uuid_option_uses_a_string_user_id(): void
{
$tester = $this->run_(['--uuid' => true]);

$migration = (string) file_get_contents(glob($this->dir . '/*_create_sessions_table.php')[0]);

$this->assertSame(0, $tester->getStatusCode());
$this->assertStringContainsString("\$table->string('user_id', 36)->nullable()->index();", $migration);
$this->assertStringNotContainsString('unsignedBigInteger(\'user_id\')', $migration);
$this->assertStringNotContainsString('{{ user_id }}', $migration);
}

public function test_it_rejects_a_schema_qualified_table_name(): void
{
Config::set('session.table', 'app.sessions');
Expand All @@ -116,7 +139,7 @@ public function test_it_rejects_an_unsafe_table_name(): void
$this->assertSame([], glob($this->dir . '/*.php') ?: []);
}

private function run_(): CommandTester
private function run_(array $input = []): CommandTester
{
$directory = $this->dir;

Expand All @@ -133,7 +156,7 @@ protected function migrationPath(): string
};

$tester = new CommandTester($command);
$tester->execute([], ['interactive' => false, 'decorated' => false]);
$tester->execute($input, ['interactive' => false, 'decorated' => false]);

return $tester;
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Session/DatabaseStoreTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -558,8 +558,8 @@ protected function migrate(): void

// As generated for a configured session.connection.
file_put_contents($file, str_replace(
['{{ schema }}', '{{ table }}'],
["Schema::connection('" . static::CONNECTION . "')->", static::TABLE],
['{{ schema }}', '{{ table }}', '{{ user_id }}'],
["Schema::connection('" . static::CONNECTION . "')->", static::TABLE, "\$table->unsignedBigInteger('user_id')->nullable()->index();"],
$stub
));

Expand Down
Loading