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
33 changes: 17 additions & 16 deletions docs/content/configuration/providers/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ providers:
enabled: true
priority: 100
overrides:
allowRecognitionOnUntrustedIp: false
sessionConcurrencyPolicy: "REJECT_NEW"
recognition:
enabled: true
signals:
- "USERNAME"
- "VIRTUAL_HOST"
eligibility:
allowOnUntrustedIp: false
```
</Tab>
<Tab value="JSON">
Expand All @@ -41,11 +42,13 @@ providers:
"enabled": true,
"priority": 100,
"overrides": {
"allowRecognitionOnUntrustedIp": false,
"sessionConcurrencyPolicy": "REJECT_NEW",
"recognition": {
"enabled": true,
"signals": ["USERNAME", "VIRTUAL_HOST"]
"signals": ["USERNAME", "VIRTUAL_HOST"],
"eligibility": {
"allowOnUntrustedIp": false
}
}
}
}
Expand Down Expand Up @@ -74,19 +77,6 @@ Configura keeps the object structure without filling every possible child field.

If this is absent, Identica uses `settings.connection.sessions.concurrencyPolicy`.

#### overrides.allowRecognitionOnUntrustedIp

<Badge type="warning">Advanced</Badge>

- Takes: `true` or `false`
- Current default: `false`
- Affects: whether this provider may use recognition-based authentication bypass from IPs blocked by `settings.connection.sessions.recognition.untrustedIps`

Leave this `false` for the normal safety posture.
Set it to `true` only when a specific provider may skip authentication from client IPs that are blocked for recognition by default.

This override applies only to that provider.

#### overrides.recognition.enabled

<Badge type="warning">Warning</Badge>
Expand All @@ -108,6 +98,17 @@ Use it when one provider should allow reconnect recognition while another should

When this field is absent or empty, the provider inherits `settings.connection.sessions.recognition.defaultSignals`.

#### overrides.recognition.eligibility.allowOnUntrustedIp

<Badge type="warning">Advanced</Badge>

- Takes: `true` or `false`
- Current default: `false`
- Affects: whether this provider stays recognition-eligible from client IPs blocked by `settings.connection.sessions.recognition.eligibility.untrustedIps`

Leave this `false` for the normal safety posture.
Set it to `true` only when this specific provider should remain recognition-eligible on client IPs that are otherwise blocked.

Recognition is intentionally disabled by default because signal matching can be unsafe in shared-network environments.
IP-based matching in particular can be exploited behind NAT, carrier-grade NAT, shared Wi-Fi, VPN exits, hosting proxies, or other setups where several different players appear to come from the same address.

Expand Down
35 changes: 19 additions & 16 deletions docs/content/configuration/settings/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -541,26 +541,26 @@ That includes NAT, carrier-grade NAT, shared Wi-Fi, VPN exits, reverse proxies,

Enable recognition only if you understand that tradeoff and accept the risk for your environment.

#### recognition.untrustedIps.enabled
#### recognition.eligibility.untrustedIps.enabled

<Badge type="warning">Warning</Badge>

- Takes: `true` or `false`
- Current default: `true`
- Affects: whether recognition-based authentication bypass is allowed for configured client IPs and CIDR ranges
- Affects: whether recognition remains eligible for configured client IPs and CIDR ranges

This guard controls recognition paths that can skip provider authentication, including reconnect recognition and provider-scoped handshake or session recognition.
This rule controls whether recognition may run at all, including reconnect recognition and provider-scoped handshake recognition.

Use this when reconnect recognition is generally acceptable, but there are client networks you do not trust for automatic recognition decisions.
The generated defaults target the common “proxy forwarded a loopback/private proxy IP instead of the real client IP” failure mode.

#### recognition.untrustedIps.entries
#### recognition.eligibility.untrustedIps.entries

<Badge type="warning">Warning</Badge>

- Takes: a list of exact IP literals or IPv4/IPv6 CIDR ranges
- Current default: `["127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]`
- Affects: which client IPs disable recognition-based authentication bypass
- Affects: which client IPs make recognition ineligible

The generated defaults cover loopback and RFC1918 private ranges, which are often a sign that Identica is behind a proxy that is forwarding the wrong source IP.

Expand Down Expand Up @@ -605,14 +605,15 @@ connection:
- "USERNAME"
- "IP"
- "VIRTUAL_HOST"
untrustedIps:
enabled: true
entries:
- "127.0.0.1"
- "::1"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
eligibility:
untrustedIps:
enabled: true
entries:
- "127.0.0.1"
- "::1"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
```
</Tab>
<Tab value="JSON">
Expand All @@ -626,9 +627,11 @@ connection:
"enabled": false,
"validity": "12h",
"defaultSignals": ["USERNAME", "IP", "VIRTUAL_HOST"],
"untrustedIps": {
"enabled": true,
"entries": ["127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
"eligibility": {
"untrustedIps": {
"enabled": true,
"entries": ["127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package me.whereareiam.identica.identity.session.recognition.eligibility;

import org.jetbrains.annotations.NotNull;

import java.util.List;

/**
* Runtime registry of recognition eligibility rules.
*
* <p>Addons may register additional rules after Identica is initialized.</p>
*
* <pre>{@code
* RecognitionEligibilityRegistry registry =
* IdenticaAPI.getService(RecognitionEligibilityRegistry.class);
* registry.register(new CorporateRule());
* }</pre>
*/
public interface RecognitionEligibilityRegistry {
/**
* Registers or replaces a rule by id.
*
* @param rule rule to register
*/
void register(@NotNull RecognitionEligibilityRule rule);

/**
* Unregisters a rule by id.
*
* @param ruleId rule id
* @return {@code true} when a rule was removed
*/
boolean unregister(@NotNull String ruleId);

/**
* Resolves the currently registered rules.
*
* @return immutable list of registered rules
*/
@NotNull List<RecognitionEligibilityRule> resolve();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package me.whereareiam.identica.identity.session.recognition.eligibility;

import me.whereareiam.identica.model.session.recognition.eligibility.RecognitionEligibilityContext;
import me.whereareiam.identica.model.session.recognition.eligibility.RecognitionEligibilityRuleDecision;
import org.jetbrains.annotations.NotNull;

/**
* Rule that contributes to recognition eligibility evaluation.
*
* <p>Rules run in ascending {@link #order()} and may abstain, allow, or block
* recognition for the current context.</p>
*
* <pre>{@code
* public final class CorporateRule implements RecognitionEligibilityRule {
* public @NotNull String id() {
* return "corporate";
* }
*
* public int order() {
* return 50;
* }
*
* public @NotNull RecognitionEligibilityRuleDecision evaluate(
* @NotNull RecognitionEligibilityContext context
* ) {
* return RecognitionEligibilityRuleDecision.abstain();
* }
* }
* }</pre>
*/
public interface RecognitionEligibilityRule {
/**
* Stable rule identifier used for registration and replacement.
*
* @return stable rule id
*/
@NotNull String id();

/**
* Evaluation order. Lower values run first.
*
* @return rule order
*/
int order();

/**
* Optional guard for context-specific rules.
*
* @param context recognition attempt context
* @return {@code true} when this rule should evaluate
*/
default boolean supports(@NotNull RecognitionEligibilityContext context) {
return true;
}

/**
* Evaluates this rule for the given context.
*
* @param context recognition attempt context
* @return per-rule decision
*/
@NotNull RecognitionEligibilityRuleDecision evaluate(@NotNull RecognitionEligibilityContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package me.whereareiam.identica.identity.session.recognition.eligibility;

import me.whereareiam.identica.model.session.recognition.eligibility.RecognitionEligibilityContext;
import me.whereareiam.identica.model.session.recognition.eligibility.RecognitionEligibilityDecision;
import org.jetbrains.annotations.NotNull;

/**
* Resolves whether recognition may be attempted for a connection context.
*
* <p>This check runs before any stored recognition snapshot is matched.
* It allows core and addon rules to block or restore recognition eligibility
* for reconnect and provider-scoped handshake recognition paths.</p>
*
* <pre>{@code
* RecognitionEligibilityDecision decision = service.evaluate(
* RecognitionEligibilityContext.builder()
* .providerId("premium")
* .attemptKind(RecognitionAttemptKind.SESSION_RECOGNITION)
* .trigger(RecognitionTrigger.AUTOMATIC)
* .build()
* );
* if (!decision.isAllowed()) {
* return false;
* }
* }</pre>
*/
public interface RecognitionEligibilityService {
/**
* Evaluates whether recognition may proceed for the given context.
*
* @param context recognition attempt context
* @return resolved eligibility decision
*/
@NotNull RecognitionEligibilityDecision evaluate(@NotNull RecognitionEligibilityContext context);
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import me.whereareiam.configura.ConfigDocument;
import me.whereareiam.configura.annotation.Merge;
import me.whereareiam.configura.merge.strategy.type.StructuralObject;
import me.whereareiam.identica.type.session.RecognitionSignal;
import me.whereareiam.identica.type.session.SessionConcurrencyPolicy;
import me.whereareiam.identica.type.session.recognition.RecognitionSignal;
import me.whereareiam.identica.type.verification.UnavailableSelectionPolicy;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -97,10 +97,6 @@ public static class ProviderEntry {
@Setter
@ToString
public static class Overrides {
/**
* Restores automatic reconnect recognition for this provider even when the client IP is configured as untrusted.
*/
private boolean allowRecognitionOnUntrustedIp;
private @Nullable SessionConcurrencyPolicy sessionConcurrencyPolicy;
private @NotNull Recognition recognition = new Recognition();

Expand All @@ -110,6 +106,14 @@ public static class Overrides {
public static class Recognition {
private @Nullable Boolean enabled;
private @NotNull List<RecognitionSignal> signals = new ArrayList<>();
private @NotNull Eligibility eligibility = new Eligibility();

@Getter
@Setter
@ToString
public static class Eligibility {
private boolean allowOnUntrustedIp;
}
}
}

Expand Down
Loading
Loading