Make it possible to validate a code.
This commit is contained in:
57
src/main.zig
57
src/main.zig
@@ -59,6 +59,13 @@ fn mainInternal() !void {
|
|||||||
const authenticator = try getAuthenticator(allocator, arg.show.?, arg.config_location);
|
const authenticator = try getAuthenticator(allocator, arg.show.?, arg.config_location);
|
||||||
const code = try authenticator.code(allocator, std.time.timestamp);
|
const code = try authenticator.code(allocator, std.time.timestamp);
|
||||||
try std.io.getStdOut().writer().print("{s}\n", .{code});
|
try std.io.getStdOut().writer().print("{s}\n", .{code});
|
||||||
|
} else if (arg.validateAuthenticator != null and arg.validateCode != null) {
|
||||||
|
const authenticator = try getAuthenticator(allocator, arg.validateAuthenticator.?, arg.config_location);
|
||||||
|
const matches = try authenticator.validate(allocator, arg.validateCode.?, std.time.timestamp);
|
||||||
|
if (!matches) {
|
||||||
|
std.process.exit(1);
|
||||||
|
}
|
||||||
|
std.process.exit(0);
|
||||||
} else {
|
} else {
|
||||||
printHelp();
|
printHelp();
|
||||||
}
|
}
|
||||||
@@ -71,6 +78,7 @@ fn printHelp() void {
|
|||||||
\\Commands:
|
\\Commands:
|
||||||
\\ list List the configured authentiators
|
\\ list List the configured authentiators
|
||||||
\\ show NAME Show the code for the authenticator with name NAME
|
\\ show NAME Show the code for the authenticator with name NAME
|
||||||
|
\\ validate NAME CODE Validate the code CODE for the authenticator with name NAME
|
||||||
\\
|
\\
|
||||||
\\Options:
|
\\Options:
|
||||||
\\ --bash Print script to set up Bash shell integration. Usage: add
|
\\ --bash Print script to set up Bash shell integration. Usage: add
|
||||||
@@ -82,11 +90,7 @@ fn printHelp() void {
|
|||||||
std.debug.print("{s}", .{msg});
|
std.debug.print("{s}", .{msg});
|
||||||
}
|
}
|
||||||
|
|
||||||
const Args = struct {
|
const Args = struct { list: bool, show: ?[]const u8, config_location: []const u8, validateAuthenticator: ?[]const u8, validateCode: ?[]const u8 };
|
||||||
list: bool,
|
|
||||||
show: ?[]const u8,
|
|
||||||
config_location: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const OtpAuthUrl = struct { name: []const u8, secretEncoded: []const u8, url: []const u8, period: u32, digits: u4, algorithm: []const u8 };
|
const OtpAuthUrl = struct { name: []const u8, secretEncoded: []const u8, url: []const u8, period: u32, digits: u4, algorithm: []const u8 };
|
||||||
|
|
||||||
@@ -148,6 +152,12 @@ const Authenticator = struct {
|
|||||||
//debug("code as 0-padded string: {s} ({d})\n", .{ result, token });
|
//debug("code as 0-padded string: {s} ({d})\n", .{ result, token });
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate(self: Authenticator, allocator: Allocator, expectedCode: []const u8, timeFunc: *const fn () i64) !bool {
|
||||||
|
const actualCode = try self.code(allocator, timeFunc);
|
||||||
|
defer allocator.free(actualCode);
|
||||||
|
return eql(u8, actualCode, expectedCode);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn zeroPad(allocator: Allocator, digits: u4, x: anytype) ![]u8 {
|
fn zeroPad(allocator: Allocator, digits: u4, x: anytype) ![]u8 {
|
||||||
@@ -176,11 +186,7 @@ fn zeroPad(allocator: Allocator, digits: u4, x: anytype) ![]u8 {
|
|||||||
const ArgumentError = error{ InvalidOtpAuthUrl, UnknownParameter, MissingConfigLocation, AuthenticatorNotFound, MissingAuthenticatorParam, FailedToOpenConfigFile, TooManyParsinErrors };
|
const ArgumentError = error{ InvalidOtpAuthUrl, UnknownParameter, MissingConfigLocation, AuthenticatorNotFound, MissingAuthenticatorParam, FailedToOpenConfigFile, TooManyParsinErrors };
|
||||||
|
|
||||||
fn parseArgs(allocator: Allocator, args: []const []const u8) !Args {
|
fn parseArgs(allocator: Allocator, args: []const []const u8) !Args {
|
||||||
var result = Args{
|
var result = Args{ .list = false, .show = null, .config_location = try configLocation(allocator), .validateAuthenticator = null, .validateCode = null };
|
||||||
.list = false,
|
|
||||||
.show = null,
|
|
||||||
.config_location = try configLocation(allocator),
|
|
||||||
};
|
|
||||||
var i: u17 = 1;
|
var i: u17 = 1;
|
||||||
while (i < args.len) : (i += 1) {
|
while (i < args.len) : (i += 1) {
|
||||||
const arg = args[i];
|
const arg = args[i];
|
||||||
@@ -189,14 +195,17 @@ fn parseArgs(allocator: Allocator, args: []const []const u8) !Args {
|
|||||||
const msg =
|
const msg =
|
||||||
\\_zig_totp_completions()
|
\\_zig_totp_completions()
|
||||||
\\{
|
\\{
|
||||||
\\ if [ "${COMP_WORDS[$COMP_CWORD-1]}" = 'show' ]
|
\\ if [ "${COMP_WORDS[$COMP_CWORD-1]}" = 'show' ] || [ "${COMP_WORDS[$COMP_CWORD-1]}" = 'validate' ]
|
||||||
\\ then
|
\\ then
|
||||||
\\ COMPREPLY=($(compgen -W "$(zig-totp list 2> /dev/null )" -- "${COMP_WORDS[$COMP_CWORD]}" ))
|
\\ COMPREPLY=($(compgen -W "$(zig-totp list 2> /dev/null )" -- "${COMP_WORDS[$COMP_CWORD]}" ))
|
||||||
|
\\ elif [ "${COMP_WORDS[$COMP_CWORD-2]}" = 'validate' ]
|
||||||
|
\\ then
|
||||||
|
\\ COMPREPLY=($(compgen -W "CODE" -- "${COMP_WORDS[$COMP_CWORD]}"))
|
||||||
\\ elif [ "${COMP_WORDS[$COMP_CWORD-1]}" = '--config' ]
|
\\ elif [ "${COMP_WORDS[$COMP_CWORD-1]}" = '--config' ]
|
||||||
\\ then
|
\\ then
|
||||||
\\ COMPREPLY=($(compgen -A file -- "${COMP_WORDS[$COMP_CWORD]}" ))
|
\\ COMPREPLY=($(compgen -A file -- "${COMP_WORDS[$COMP_CWORD]}" ))
|
||||||
\\ else
|
\\ else
|
||||||
\\ COMPREPLY=($(compgen -W "list show --bash --config" -- "${COMP_WORDS[$COMP_CWORD]}" ))
|
\\ COMPREPLY=($(compgen -W "list show validate --bash --config" -- "${COMP_WORDS[$COMP_CWORD]}" ))
|
||||||
\\ fi
|
\\ fi
|
||||||
\\}
|
\\}
|
||||||
\\complete -F _zig_totp_completions zig-totp
|
\\complete -F _zig_totp_completions zig-totp
|
||||||
@@ -220,6 +229,14 @@ fn parseArgs(allocator: Allocator, args: []const []const u8) !Args {
|
|||||||
return error.MissingAuthenticatorParam;
|
return error.MissingAuthenticatorParam;
|
||||||
}
|
}
|
||||||
result.show = args[i];
|
result.show = args[i];
|
||||||
|
} else if (eql(u8, "validate", arg)) {
|
||||||
|
i += 2;
|
||||||
|
if (i >= args.len) {
|
||||||
|
std.debug.print("expected two parameters for 'validate'. The name for an authenticator and a code to validate.\n", .{});
|
||||||
|
return error.MissingValidateParam;
|
||||||
|
}
|
||||||
|
result.validateAuthenticator = args[i - 1];
|
||||||
|
result.validateCode = args[i];
|
||||||
} else {
|
} else {
|
||||||
std.debug.print("unknown argument: {s}\n", .{arg});
|
std.debug.print("unknown argument: {s}\n", .{arg});
|
||||||
return ArgumentError.UnknownParameter;
|
return ArgumentError.UnknownParameter;
|
||||||
@@ -577,3 +594,19 @@ test "secret is not Base32 - invalid character" {
|
|||||||
const actualError = authenticator.code(std.testing.allocator, _closure_return_1725695340);
|
const actualError = authenticator.code(std.testing.allocator, _closure_return_1725695340);
|
||||||
try std.testing.expectError(Base32Error.InvalidCharacter, actualError);
|
try std.testing.expectError(Base32Error.InvalidCharacter, actualError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "testcases for 'validate'" {
|
||||||
|
const secretForSha1 = "GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ"; // plain: "12345678901234567890" hex: "3132333435363738393031323334353637383930"
|
||||||
|
|
||||||
|
try testValidate(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_59, "94287082", true);
|
||||||
|
|
||||||
|
try testValidate(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_1111111109, "07081804", true);
|
||||||
|
|
||||||
|
try testValidate(OtpAuthUrl{ .name = "", .secretEncoded = secretForSha1, .url = "", .period = 30, .digits = 8, .algorithm = "SHA1" }, _closure_return_1111111109, "12345678", false);
|
||||||
|
}
|
||||||
|
fn testValidate(otpAuthUrl: OtpAuthUrl, timeFunc: *const fn () i64, expected: []const u8, expectedMatch: bool) !void {
|
||||||
|
const authenticator = Authenticator{ .url = otpAuthUrl };
|
||||||
|
|
||||||
|
const match = try authenticator.validate(std.testing.allocator, expected, timeFunc);
|
||||||
|
try std.testing.expectEqual(expectedMatch, match);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user