Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public enum PlayerStatePermission implements PermissionNode {
*/
ALLOW_MULTIPLE_ACCOUNTS("authme.allowmultipleaccounts", DefaultPermission.OP_ONLY),

/**
* Disables sessions for the player, requiring authentication on every join.
*/
DISABLE_SESSION("authme.session.disable", DefaultPermission.NOT_ALLOWED),

/**
* Permission to bypass the purging process.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ private void doPerformLogin(Player player, PlayerAuth auth, boolean proxyInitiat
// makes player loggedin
playerCache.updatePlayer(auth);
dataSource.setLogged(name);
sessionService.grantSession(name);
sessionService.grantSession(player);

if (bungeeSender.isEnabled()) {
// As described at https://www.spigotmc.org/wiki/bukkit-bungee-plugin-messaging-channel/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import fr.xephi.authme.initialization.Reloadable;
import fr.xephi.authme.output.ConsoleLoggerFactory;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.settings.properties.PluginSettings;
import fr.xephi.authme.util.PlayerUtils;
import org.bukkit.entity.Player;
Expand All @@ -24,14 +26,17 @@ public class SessionService implements Reloadable {
private final CommonService service;
private final BukkitService bukkitService;
private final DataSource database;
private final PermissionsManager permissionsManager;

private boolean isEnabled;

@Inject
SessionService(CommonService service, BukkitService bukkitService, DataSource database) {
SessionService(CommonService service, BukkitService bukkitService, DataSource database,
PermissionsManager permissionsManager) {
this.service = service;
this.bukkitService = bukkitService;
this.database = database;
this.permissionsManager = permissionsManager;
reload();
}

Expand All @@ -46,6 +51,9 @@ public boolean canResumeSession(Player player) {
if (isEnabled && database.hasSession(name)) {
database.setUnlogged(name);
database.revokeSession(name);
if (permissionsManager.hasPermission(player, PlayerStatePermission.DISABLE_SESSION)) {
return false;
}
SessionState state = fetchSessionStatus(name, database.getAuth(name), PlayerUtils.getPlayerIp(player));
if (state.equals(SessionState.VALID)) {
RestoreSessionEvent event = bukkitService.createAndCallEvent(
Expand All @@ -61,13 +69,15 @@ public boolean canResumeSession(Player player) {
/**
* Returns whether the given player name has a valid resumable session for the supplied IP address.
* This check is side-effect free and can be used before a Bukkit {@link Player} instance exists.
* The permission is checked again against the player in {@link #canResumeSession(Player)} before login.
*
* @param playerName the player name
* @param ipAddress the player's IP address
* @return true if the player's session is currently valid, false otherwise
*/
public boolean hasValidSession(String playerName, String ipAddress) {
if (!isEnabled || ipAddress == null || !database.hasSession(playerName)) {
if (!isEnabled || ipAddress == null || !database.hasSession(playerName)
|| permissionsManager.hasPermissionOffline(playerName, PlayerStatePermission.DISABLE_SESSION)) {
return false;
}

Expand Down Expand Up @@ -103,9 +113,9 @@ private SessionState fetchSessionStatus(String playerName, PlayerAuth auth, Stri
return SessionState.OUTDATED;
}

public void grantSession(String name) {
if (isEnabled) {
database.grantSession(name);
public void grantSession(Player player) {
if (isEnabled && !permissionsManager.hasPermission(player, PlayerStatePermission.DISABLE_SESSION)) {
database.grantSession(player.getName());
}
}

Expand Down
4 changes: 4 additions & 0 deletions authme-core/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ permissions:
authme.player.unregister:
description: Command permission to unregister.
default: true
authme.session.disable:
description: Disables sessions for the player, requiring authentication on every
join.
default: false
authme.vip:
description: When the server is full and someone with this permission joins the
server, someone will be kicked.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import fr.xephi.authme.datasource.DataSource;
import fr.xephi.authme.events.RestoreSessionEvent;
import fr.xephi.authme.message.MessageKey;
import fr.xephi.authme.permission.PermissionsManager;
import fr.xephi.authme.permission.PlayerStatePermission;
import fr.xephi.authme.settings.properties.PluginSettings;
import org.bukkit.entity.Player;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -41,6 +43,8 @@ class SessionServiceTest {
private CommonService commonService;
@Mock
private BukkitService bukkitService;
@Mock
private PermissionsManager permissionsManager;

@BeforeAll
static void initLogger() {
Expand All @@ -50,7 +54,7 @@ static void initLogger() {
@BeforeEach
void createSessionService() {
given(commonService.getProperty(PluginSettings.SESSIONS_ENABLED)).willReturn(true);
sessionService = new SessionService(commonService, bukkitService, dataSource);
sessionService = new SessionService(commonService, bukkitService, dataSource, permissionsManager);
}

@Test
Expand Down Expand Up @@ -81,7 +85,8 @@ void shouldCheckIfUserHasSession() {

// then
assertThat(result, equalTo(false));
verify(commonService, only()).getProperty(PluginSettings.SESSIONS_ENABLED);
verify(commonService).getProperty(PluginSettings.SESSIONS_ENABLED);
verifyNoMoreInteractions(commonService);
verify(dataSource, only()).hasSession(name);
}

Expand Down Expand Up @@ -222,6 +227,110 @@ void shouldHandlePlayerAuthWithNullLastIp() {
verify(dataSource).revokeSession(name);
}

@Test
void shouldNotResumeSessionForPlayerWithDisabledSessionPermission() {
// given
String name = "Bobby";
Player player = mock(Player.class);
given(player.getName()).willReturn(name);
given(dataSource.hasSession(name)).willReturn(true);
given(permissionsManager.hasPermission(player, PlayerStatePermission.DISABLE_SESSION)).willReturn(true);

// when
boolean result = sessionService.canResumeSession(player);

// then
assertThat(result, equalTo(false));
verify(dataSource).hasSession(name);
verify(dataSource).setUnlogged(name);
verify(dataSource).revokeSession(name);
verifyNoMoreInteractions(dataSource);
verifyNoInteractions(bukkitService);
}

@Test
void shouldResumeSessionForPlayerWithoutDisabledSessionPermission() {
// given
String name = "Bobby";
String ip = "127.3.12.15";
Player player = mockPlayerWithNameAndIp(name, ip);
given(commonService.getProperty(PluginSettings.SESSIONS_TIMEOUT)).willReturn(8);
given(dataSource.hasSession(name)).willReturn(true);
PlayerAuth auth = PlayerAuth.builder()
.name(name)
.lastLogin(System.currentTimeMillis() - 60 * 1000)
.lastIp(ip).build();
given(dataSource.getAuth(name)).willReturn(auth);
given(bukkitService.createAndCallEvent(any(Function.class))).willReturn(new RestoreSessionEvent(player, false));

// when
boolean result = sessionService.canResumeSession(player);

// then
assertThat(result, equalTo(true));
}

@Test
void shouldNotReportValidSessionForPlayerWithDisabledSessionPermission() {
// given
given(dataSource.hasSession("Bobby")).willReturn(true);
given(permissionsManager.hasPermissionOffline("Bobby", PlayerStatePermission.DISABLE_SESSION))
.willReturn(true);

// when
boolean result = sessionService.hasValidSession("Bobby", "127.3.12.15");

// then
assertThat(result, equalTo(false));
verify(dataSource, only()).hasSession("Bobby");
}

@Test
void shouldReportValidSessionForPlayerWithoutDisabledSessionPermission() {
// given
String name = "Bobby";
String ip = "127.3.12.15";
given(dataSource.hasSession(name)).willReturn(true);
given(commonService.getProperty(PluginSettings.SESSIONS_TIMEOUT)).willReturn(8);
given(dataSource.getAuth(name)).willReturn(PlayerAuth.builder()
.name(name)
.lastLogin(System.currentTimeMillis() - 60 * 1000)
.lastIp(ip).build());

// when
boolean result = sessionService.hasValidSession(name, ip);

// then
assertThat(result, equalTo(true));
verify(permissionsManager).hasPermissionOffline(name, PlayerStatePermission.DISABLE_SESSION);
}

@Test
void shouldNotGrantSessionToPlayerWithDisabledSessionPermission() {
// given
Player player = mock(Player.class);
given(permissionsManager.hasPermission(player, PlayerStatePermission.DISABLE_SESSION)).willReturn(true);

// when
sessionService.grantSession(player);

// then
verifyNoInteractions(dataSource);
}

@Test
void shouldGrantSessionToPlayerWithoutDisabledSessionPermission() {
// given
Player player = mock(Player.class);
given(player.getName()).willReturn("bobby");

// when
sessionService.grantSession(player);

// then
verify(dataSource, only()).grantSession("bobby");
}

private static Player mockPlayerWithNameAndIp(String name, String ip) {
Player player = mock(Player.class);
given(player.getName()).willReturn(name);
Expand Down
4 changes: 4 additions & 0 deletions authme-core/src/test/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,10 @@ permissions:
authme.player.unregister:
description: Command permission to unregister.
default: true
authme.session.disable:
description: Disables sessions for the player, requiring authentication on every
join.
default: false
authme.vip:
description: When the server is full and someone with this permission joins the
server, someone will be kicked.
Expand Down
4 changes: 4 additions & 0 deletions authme-folia/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,10 @@ permissions:
authme.player.unregister:
description: Command permission to unregister.
default: true
authme.session.disable:
description: Disables sessions for the player, requiring authentication on every
join.
default: false
authme.vip:
description: When the server is full and someone with this permission joins the
server, someone will be kicked.
Expand Down
4 changes: 4 additions & 0 deletions authme-paper/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ permissions:
authme.player.unregister:
description: Command permission to unregister.
default: true
authme.session.disable:
description: Disables sessions for the player, requiring authentication on every
join.
default: false
authme.vip:
description: When the server is full and someone with this permission joins the
server, someone will be kicked.
Expand Down
4 changes: 4 additions & 0 deletions authme-spigot-1.21/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ permissions:
authme.player.unregister:
description: Command permission to unregister.
default: true
authme.session.disable:
description: Disables sessions for the player, requiring authentication on every
join.
default: false
authme.vip:
description: When the server is full and someone with this permission joins the
server, someone will be kicked.
Expand Down
4 changes: 4 additions & 0 deletions authme-spigot-legacy/src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,10 @@ permissions:
authme.player.unregister:
description: Command permission to unregister.
default: true
authme.session.disable:
description: Disables sessions for the player, requiring authentication on every
join.
default: false
authme.vip:
description: When the server is full and someone with this permission joins the
server, someone will be kicked.
Expand Down
4 changes: 2 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Tue May 19 08:22:43 CEST 2026. See authme-tools/src/test/java/tools/docs/config/config.tpl.md -->
<!-- File auto-generated on Wed Sep 23 23:23:54 CEST 2026. See authme-tools/src/test/java/tools/docs/config/config.tpl.md -->

## AuthMe Configuration
The first time you run AuthMe it will create a config.yml file in the plugins/AuthMe folder,
Expand Down Expand Up @@ -649,4 +649,4 @@ To change settings on a running server, save your changes to config.yml and use

---

This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Tue May 19 08:22:43 CEST 2026
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Sep 23 23:23:54 CEST 2026
6 changes: 3 additions & 3 deletions docs/permission_nodes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!-- AUTO-GENERATED FILE! Do not edit this directly -->
<!-- File auto-generated on Wed May 06 10:21:25 CEST 2026. See authme-tools/src/test/java/tools/docs/permissions/permission_nodes.tpl.md -->
<!-- File auto-generated on Wed Sep 23 23:24:40 CEST 2026. See authme-tools/src/test/java/tools/docs/permissions/permission_nodes.tpl.md -->

## AuthMe Permission Nodes
The following are the permission nodes that are currently supported by the latest dev builds.
Expand Down Expand Up @@ -76,9 +76,9 @@ and Paper 1.21 builds.
- **authme.player.totpadd** – Permission to enable two-factor authentication.
- **authme.player.totpremove** – Permission to disable two-factor authentication.
- **authme.player.unregister** – Command permission to unregister.
- **authme.session.disable** – Disables sessions for the player, requiring authentication on every join.
- **authme.vip** – When the server is full and someone with this permission joins the server, someone will be kicked.


---

This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed May 06 10:21:25 CEST 2026
This page was automatically generated on the [AuthMe/AuthMeReloaded repository](https://github.com/AuthMe/AuthMeReloaded/tree/master/docs/) on Wed Sep 23 23:24:40 CEST 2026
Loading