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
3 changes: 3 additions & 0 deletions .idea/module-auth.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .idea/php.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .phpstan/config.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
parameters:
level: 0
level: 1
paths:
- ../helpers
- ../src
Expand Down
1 change: 1 addition & 0 deletions admin/controllers/Accounts.php
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ public function edit(): void

// --------------------------------------------------------------------------

/** @var \Nails\Auth\Resource\User|null $oUser */
$oUser = $oUserModel->getById($oUri->segment(5));

if (empty($oUser)) {
Expand Down
2 changes: 1 addition & 1 deletion admin/controllers/Merge.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public static function announce(): Nav|array|null
->addAction('Merge Users');
}

return $oNavGroup ?? null;
return null;
}

// --------------------------------------------------------------------------
Expand Down
10 changes: 5 additions & 5 deletions admin/controllers/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Nails\Admin\Helper;
use Nails\Auth\Constants;
use Nails\Auth\Controller\BaseAdmin;
use Nails\Auth\Service\Passkey;
use Nails\Auth\Service\SocialSignOn;
use Nails\Common\Service\AppSetting;
use Nails\Common\Service\Asset;
Expand Down Expand Up @@ -113,6 +114,7 @@ public function index(): void

if (userHasPermission('admin:auth:settings:update:login')) {
$aSettings['user_login_captcha_enabled'] = (bool) $oInput->post('user_login_captcha_enabled');
$aSettings[Passkey::SETTING_ENABLED] = (bool) $oInput->post(Passkey::SETTING_ENABLED);
}

// --------------------------------------------------------------------------
Expand Down Expand Up @@ -219,11 +221,9 @@ public function index(): void

$bRollback = false;

if (!empty($aSettings)) {
if (!$oAppSettingService->set($aSettings, 'auth')) {
$error = $oAppSettingService->lastError();
$bRollback = true;
}
if (!$oAppSettingService->set($aSettings, 'auth')) {
$error = $oAppSettingService->lastError();
$bRollback = true;
}

if (!empty($aSettingsEncrypted)) {
Expand Down
14 changes: 14 additions & 0 deletions admin/language/english/admin_accounts_lang.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,17 @@
$lang['accounts_delete_error_selfie'] = 'You can\'t delete yourself.';
$lang['accounts_delete_success'] = 'User %s was deleted successfully.';
$lang['accounts_delete_error'] = 'There was a problem deleting %s.';

// --------------------------------------------------------------------------

// Passkeys
$lang['accounts_edit_passkeys_none'] = 'This user has not registered any passkeys.';
$lang['accounts_edit_passkeys_disabled'] = 'Passkeys are not currently enabled for this site; existing passkeys are listed below but cannot be used to sign in.';
$lang['accounts_edit_passkeys_warning'] = 'Revoking a passkey cannot be undone; the user will have to register the device again.';
$lang['accounts_edit_passkeys_label'] = 'Name';
$lang['accounts_edit_passkeys_added'] = 'Added';
$lang['accounts_edit_passkeys_last_used'] = 'Last used';
$lang['accounts_edit_passkeys_never_used'] = 'Never';
$lang['accounts_edit_passkeys_details'] = 'Details';
$lang['accounts_edit_passkeys_revoke'] = 'Revoke';
$lang['accounts_edit_passkeys_synced'] = 'Synced';
107 changes: 107 additions & 0 deletions admin/views/Accounts/edit/inc-passkeys.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

/**
* @var \Nails\Auth\Resource\User $oUser
* @var \Nails\Auth\Resource\User\Passkey[] $aPasskeys
* @var bool $bEnabled
*/

use Nails\Admin\Helper;

if (!$bEnabled) {
?>
<div class="alert alert-warning">
<?=lang('accounts_edit_passkeys_disabled')?>
</div>
<?php
}

if (!empty($aPasskeys)) {
?>
<div class="alert alert-warning">
<?=lang('accounts_edit_passkeys_warning')?>
</div>
<?php
}

?>
<div class="table-responsive">
<table class="table table-striped table-hover table-bordered table-responsive">
<thead class="table-dark">
<tr>
<th class="field field--label">
<?=lang('accounts_edit_passkeys_label')?>
</th>
<th class="field field--added" width="200">
<?=lang('accounts_edit_passkeys_added')?>
</th>
<th class="field field--last-used" width="200">
<?=lang('accounts_edit_passkeys_last_used')?>
</th>
<th class="field field--synced boolean" width="150">
<?=lang('accounts_edit_passkeys_synced')?>
</th>
<th class="field field--transports">
<?=lang('accounts_edit_passkeys_details')?>
</th>
<th class="actions text-center" width="100">
<?=lang('accounts_edit_passkeys_revoke')?>
</th>
</tr>
</thead>
<tbody>
<?php

if (empty($aPasskeys)) {
?>
<tr>
<td colspan="6" class="no-data">
<?=lang('accounts_edit_passkeys_none')?>
</td>
</tr>
<?php

} else {
foreach ($aPasskeys as $oPasskey) {

$aTransports = $oPasskey->getTransports();

?>
<tr>
<td class="field field--label">
<?=htmlspecialchars((string) $oPasskey->label)?>
</td>
<?php

echo Helper::loadDateTimeCell((string) $oPasskey->created);
echo Helper::loadDateTimeCell($oPasskey->last_used ? (string) $oPasskey->last_used : null);
echo Helper::loadBoolCell($oPasskey->is_backed_up);

/**
* The AAGUID names the model of authenticator; it is only of use
* when supporting a user, so it sits under the transports rather
* than taking a column of its own.
*/
echo Helper::loadCellAuto(
$aTransports
? htmlspecialchars(implode(', ', $aTransports))
: null,
'field field--transports',
$oPasskey->aaguid
? '<br><small class="text-muted">' . htmlspecialchars($oPasskey->aaguid) . '</small>'
: ''
);

?>
<td class="actions text-center">
<input type="checkbox" name="passkey_revoke[]" value="<?=(int) $oPasskey->id?>">
</td>
</tr>
<?php
}
}

?>
</tbody>
</table>
</div>
38 changes: 38 additions & 0 deletions admin/views/Settings/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,50 @@
!userHasPermission('admin:auth:settings:update:login') ? null : [
'label' => 'Login',
'content' => function () {

/** @var \Nails\Auth\Service\Passkey $oPasskeyService */
$oPasskeyService = \Nails\Factory::service('Passkey', \Nails\Auth\Constants::MODULE_SLUG);

echo form_field_boolean([
'key' => 'user_login_captcha_enabled',
'label' => 'Captcha',
'default' => (bool) appSetting('user_login_captcha_enabled', 'auth'),
'info' => anchor('admin/captcha/settings', 'Manage captcha settings here'),
]);

echo form_field_boolean([
'key' => \Nails\Auth\Service\Passkey::SETTING_ENABLED,
'label' => 'Passkeys',
'default' => (bool) appSetting(\Nails\Auth\Service\Passkey::SETTING_ENABLED, 'auth'),
'info' => 'Allows users to sign in with a passkey, and to register one against their account.',
]);

/**
* Read only: both are derived from BASE_URL and can only be overridden in
* the app's config, because changing the Relying Party ID invalidates every
* passkey already registered.
*/

?>
<div class="alert alert-info">
<p>
<strong>Relying Party ID:</strong>
<code><?=htmlspecialchars($oPasskeyService->getRpId())?></code>
</p>
<p>
<strong>Permitted origins:</strong>
<code><?=htmlspecialchars(implode(', ', $oPasskeyService->getAllowedOrigins()))?></code>
</p>
<p class="mb-0">
<small>
Derived from <code>BASE_URL</code>. Override with
<code><?=\Nails\Auth\Service\Passkey::CONFIG_RP_ID?></code> and
<code><?=\Nails\Auth\Service\Passkey::CONFIG_ALLOWED_ORIGINS?></code>.
Changing the Relying Party ID invalidates existing passkeys.
</small>
</p>
</div>
<?php
},
],

Expand Down
2 changes: 1 addition & 1 deletion assets/css/styles.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading