Skip to content

Commit 7de2d99

Browse files
committed
impleemnt auth / connection commands
1 parent 6e2c958 commit 7de2d99

File tree

16 files changed

+188
-5
lines changed

16 files changed

+188
-5
lines changed

docs/src/guide/overview/commands.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,24 @@ Follows [Redis's commands](https://redis.io/commands).
44

55
Implemented commands:
66

7+
<details>
8+
<summary>Server</summary>
9+
10+
- INFO
11+
- FLUSHDB
12+
- TIME
13+
14+
</details>
15+
716
<details>
817
<summary>Connection</summary>
918

19+
- AUTH
1020
- ECHO
1121
- PING
1222
- QUIT
23+
- CLIENT ID
24+
- CLIENT INFO
1325

1426
</details>
1527

docs/src/guide/overview/install.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ Parameters:
3030
-aof <Boolean> enable append-only-file (default: false)
3131
-ai <Integer> define append-only interval in ms (default: 1000)
3232
-dir <String> working directory (default: ./)
33+
-pw <String> authenticate password (default: none - no password)
3334

3435
## Docker
3536

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package dev.keva.server.command.impl.connection;
2+
3+
import dev.keva.ioc.annotation.Autowired;
4+
import dev.keva.ioc.annotation.Component;
5+
import dev.keva.protocol.resp.reply.ErrorReply;
6+
import dev.keva.protocol.resp.reply.Reply;
7+
import dev.keva.protocol.resp.reply.StatusReply;
8+
import dev.keva.server.command.annotation.CommandImpl;
9+
import dev.keva.server.command.annotation.Execute;
10+
import dev.keva.server.command.annotation.ParamLength;
11+
import dev.keva.server.command.impl.connection.manager.AuthManager;
12+
import dev.keva.server.config.KevaConfig;
13+
import io.netty.channel.ChannelHandlerContext;
14+
15+
@Component
16+
@CommandImpl("auth")
17+
@ParamLength(1)
18+
public class Auth {
19+
private final KevaConfig kevaConfig;
20+
private final AuthManager authManager;
21+
22+
@Autowired
23+
public Auth(KevaConfig kevaConfig, AuthManager authManager) {
24+
this.kevaConfig = kevaConfig;
25+
this.authManager = authManager;
26+
}
27+
28+
@Execute
29+
public Reply<?> execute(byte[] password, ChannelHandlerContext ctx) {
30+
String passwordString = new String(password);
31+
if (kevaConfig.getPassword().equals(passwordString)) {
32+
authManager.authenticate(ctx.channel());
33+
return new StatusReply("OK");
34+
}
35+
return new ErrorReply("ERR WRONGPASS invalid password.");
36+
}
37+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package dev.keva.server.command.impl.connection;
2+
3+
import dev.keva.ioc.annotation.Component;
4+
import dev.keva.protocol.resp.reply.BulkReply;
5+
import dev.keva.protocol.resp.reply.ErrorReply;
6+
import dev.keva.protocol.resp.reply.Reply;
7+
import dev.keva.server.command.annotation.CommandImpl;
8+
import dev.keva.server.command.annotation.Execute;
9+
import dev.keva.server.command.annotation.ParamLength;
10+
import io.netty.channel.ChannelHandlerContext;
11+
12+
@Component
13+
@CommandImpl("client")
14+
@ParamLength(1)
15+
public class Client {
16+
@Execute
17+
public Reply<?> execute(byte[] param, ChannelHandlerContext ctx) {
18+
String paramStr = new String(param);
19+
if (paramStr.equalsIgnoreCase("id")) {
20+
return new BulkReply(ctx.channel().id().asShortText());
21+
} else if (paramStr.equalsIgnoreCase("info")) {
22+
String info = String.format("id=%s, addr=%s\n", ctx.channel().id().asShortText(), ctx.channel().remoteAddress().toString());
23+
return new BulkReply(info);
24+
} else {
25+
return new ErrorReply("ERROR Unsupported query");
26+
}
27+
}
28+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.keva.server.command.impl.connection.manager;
2+
3+
import dev.keva.ioc.annotation.Component;
4+
import io.netty.channel.Channel;
5+
6+
import java.util.concurrent.ConcurrentHashMap;
7+
import java.util.concurrent.ConcurrentMap;
8+
9+
@Component
10+
public class AuthManager {
11+
private final ConcurrentMap<Channel, Boolean> auths = new ConcurrentHashMap<>();
12+
13+
public boolean isAuthenticated(Channel channel) {
14+
return auths.get(channel) != null && auths.get(channel);
15+
}
16+
17+
public void authenticate(Channel channel) {
18+
auths.put(channel, true);
19+
}
20+
21+
public void unAuthenticate(Channel channel) {
22+
auths.remove(channel);
23+
}
24+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package dev.keva.server.command.impl.server;
2+
3+
import dev.keva.ioc.annotation.Autowired;
4+
import dev.keva.ioc.annotation.Component;
5+
import dev.keva.protocol.resp.reply.StatusReply;
6+
import dev.keva.server.command.annotation.CommandImpl;
7+
import dev.keva.server.command.annotation.Execute;
8+
import dev.keva.server.command.annotation.ParamLength;
9+
import dev.keva.store.KevaDatabase;
10+
11+
@Component
12+
@CommandImpl("flushdb")
13+
@ParamLength(type = ParamLength.Type.AT_MOST, value = 1)
14+
public class FlushDB {
15+
private final KevaDatabase database;
16+
17+
@Autowired
18+
public FlushDB(KevaDatabase database) {
19+
this.database = database;
20+
}
21+
22+
@Execute
23+
public StatusReply execute(byte[] ignored) {
24+
database.flush();
25+
return StatusReply.OK;
26+
}
27+
}

server/src/main/java/dev/keva/server/command/impl/connection/Info.java renamed to server/src/main/java/dev/keva/server/command/impl/server/Info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dev.keva.server.command.impl.connection;
1+
package dev.keva.server.command.impl.server;
22

33
import dev.keva.ioc.annotation.Component;
44
import dev.keva.protocol.resp.reply.BulkReply;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package dev.keva.server.command.impl.server;
2+
3+
import dev.keva.ioc.annotation.Component;
4+
import dev.keva.protocol.resp.reply.BulkReply;
5+
import dev.keva.protocol.resp.reply.MultiBulkReply;
6+
import dev.keva.server.command.annotation.CommandImpl;
7+
import dev.keva.server.command.annotation.Execute;
8+
import dev.keva.server.command.annotation.ParamLength;
9+
10+
@Component
11+
@CommandImpl("time")
12+
@ParamLength(0)
13+
public class Time {
14+
@Execute
15+
public MultiBulkReply execute(byte[] ignored) {
16+
BulkReply[] replies = new BulkReply[1];
17+
replies[0] = new BulkReply(Long.toString(System.currentTimeMillis() / 1000));
18+
return new MultiBulkReply(replies);
19+
}
20+
}

server/src/main/java/dev/keva/server/command/mapping/CommandMapper.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import dev.keva.server.command.annotation.Mutate;
1313
import dev.keva.server.command.annotation.ParamLength;
1414
import dev.keva.server.aof.AOFContainer;
15+
import dev.keva.server.command.impl.connection.manager.AuthManager;
1516
import dev.keva.server.command.impl.transaction.manager.TransactionManager;
1617
import dev.keva.server.config.KevaConfig;
1718
import dev.keva.store.KevaDatabase;
@@ -38,6 +39,9 @@ public class CommandMapper {
3839
@Autowired
3940
private TransactionManager txManager;
4041

42+
@Autowired
43+
private AuthManager authManager;
44+
4145
@Autowired
4246
private KevaDatabase database;
4347

@@ -60,8 +64,17 @@ public void init() {
6064
val paramLengthType = aClass.getAnnotation(ParamLength.class) != null ? aClass.getAnnotation(ParamLength.class).type() : null;
6165
val instance = context.getBean(aClass);
6266
val isMutate = aClass.getAnnotation(Mutate.class) != null;
67+
val password = kevaConfig.getPassword();
68+
val isAuthEnabled = password != null && password.length() > 0;
6369

6470
methods.put(new BytesKey(name.getBytes()), (ctx, command) -> {
71+
if (isAuthEnabled && !Arrays.equals(command.getName(), "auth".getBytes())) {
72+
boolean authenticated = authManager.isAuthenticated(ctx.channel());
73+
if (!authenticated) {
74+
return new ErrorReply("ERR NOAUTH Authentication required.");
75+
}
76+
}
77+
6578
if (ctx != null) {
6679
val txContext = txManager.getTransactions().get(ctx.channel());
6780
if (txContext != null && txContext.isQueuing()) {

server/src/main/java/dev/keva/server/config/KevaConfig.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ public class KevaConfig {
4040
@CliProp(name = "dir", type = CliPropType.VAL)
4141
private String workDirectory;
4242

43+
@ConfigProp(name = "requirepass", defaultVal = "")
44+
@CliProp(name = "pw", type = CliPropType.VAL)
45+
private String password;
46+
4347
/**
4448
* @return KevaConfig with sensible defaults
4549
*/

0 commit comments

Comments
 (0)