better parsing of the otpauth url
Multiple parameters can be parsed. And we can set the period and digits via the url.
This commit is contained in:
124
src/main.zig
124
src/main.zig
@@ -2,12 +2,15 @@ const std = @import("std");
|
||||
const info = std.log.info;
|
||||
const debug = std.log.debug;
|
||||
const print = std.debug.print;
|
||||
const eql = std.mem.eql;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const testing = std.testing;
|
||||
const expect = testing.expect;
|
||||
const ArrayList = std.ArrayList;
|
||||
const Base32 = @import("base32.zig").Base32;
|
||||
|
||||
// otpauth://totp/AWS+Dev?secret=47STA47VFCMMLLWOLHWO3KY7MYNC36MLCDTHOLIYKJCTTSSAMKVM7YA3VWT2AJEP&digits=6&icon=Amazon
|
||||
// otpauth://totp/Gitea%20%28git.lucares.de%29:andi?algorithm=SHA1&digits=6&issuer=Gitea&period=30&secret=MSP53Q672UJMSCLQVRCJKMMZKK7MWSMYFL77OQ24JBM65RZWY7F2Y45FMTNLYM36
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
@@ -24,6 +27,9 @@ pub fn main() !void {
|
||||
for (0..names.items.len) |i| {
|
||||
std.debug.print("{s}\n", .{names.items[i]});
|
||||
}
|
||||
} else if (arg.show != null) {
|
||||
const authenticator = try getAuthenticator(allocator, arg.show.?, arg.config_location);
|
||||
debug("number for {s}", .{try authenticator.code()});
|
||||
} else {
|
||||
printHelp();
|
||||
}
|
||||
@@ -37,6 +43,7 @@ fn printHelp() void {
|
||||
\\
|
||||
\\Commands:
|
||||
\\ list List the configured authentiators
|
||||
\\ show NAME Show the code for the authenticator with name NAME
|
||||
\\
|
||||
\\Options:
|
||||
\\ --config path The path to a config file (default is $XDG_CONFIG_HOME/zig-totp or $HOME/.zig-totp).
|
||||
@@ -47,35 +54,55 @@ fn printHelp() void {
|
||||
|
||||
const Args = struct {
|
||||
list: bool,
|
||||
show: ?[]const u8,
|
||||
config_location: []const u8,
|
||||
};
|
||||
|
||||
const OtpAuthUrl = struct {
|
||||
name: []const u8,
|
||||
secretEncoded: []const u8,
|
||||
url: []const u8,
|
||||
const OtpAuthUrl = struct { name: []const u8, secretEncoded: []const u8, url: []const u8, period: u32, digits: u4 };
|
||||
|
||||
const Authenticator = struct {
|
||||
url: OtpAuthUrl,
|
||||
|
||||
pub fn code(self: Authenticator) ![]const u8 {
|
||||
if (self.url.name.len > 0) {}
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const secret = try Base32.decodeU8(allocator, self.url.secretEncoded);
|
||||
if (false) {
|
||||
debug("secret: {s}\n", .{secret});
|
||||
}
|
||||
|
||||
return "code";
|
||||
}
|
||||
};
|
||||
|
||||
const ArgumentError = error{ InvalidOtpAuthUrl, UnknownParameter, MissingConfigLocation };
|
||||
const ArgumentError = error{ InvalidOtpAuthUrl, UnknownParameter, MissingConfigLocation, AuthenticatorNotFound };
|
||||
|
||||
fn parseArgs(allocator: Allocator, args: []const []const u8) !Args {
|
||||
var result = Args{
|
||||
.list = false,
|
||||
.show = null,
|
||||
.config_location = try configLocation(allocator),
|
||||
};
|
||||
var i: u17 = 1;
|
||||
while (i < args.len) : (i += 1) {
|
||||
const arg = args[i];
|
||||
|
||||
if (std.mem.eql(u8, "--config", arg)) {
|
||||
if (eql(u8, "--config", arg)) {
|
||||
i += 1;
|
||||
if (i >= args.len) {
|
||||
return error.MissingConfigLocation;
|
||||
}
|
||||
const config_location = args[i];
|
||||
result.config_location = config_location;
|
||||
} else if (std.mem.eql(u8, "list", arg)) {
|
||||
result.config_location = args[i];
|
||||
} else if (eql(u8, "list", arg)) {
|
||||
result.list = true;
|
||||
} else if (eql(u8, "show", arg)) {
|
||||
i += 1;
|
||||
if (i >= args.len) {
|
||||
return error.MissingConfigLocation;
|
||||
}
|
||||
result.show = args[i];
|
||||
} else {
|
||||
std.debug.print("unknown parameter: {s}\n", .{arg});
|
||||
return ArgumentError.UnknownParameter;
|
||||
@@ -84,23 +111,57 @@ fn parseArgs(allocator: Allocator, args: []const []const u8) !Args {
|
||||
return result;
|
||||
}
|
||||
|
||||
// todo use a real url parser
|
||||
fn parseOtpAuthUrl(url: []const u8) !OtpAuthUrl {
|
||||
const index = std.mem.indexOf(u8, url, "otpauth://totp/");
|
||||
if (index != 0) {
|
||||
const uri = try std.Uri.parse(url);
|
||||
|
||||
if (!eql(u8, uri.scheme, "otpauth")) {
|
||||
return error.InvalidOtpAuthUrl;
|
||||
}
|
||||
|
||||
var it = std.mem.splitSequence(u8, url[15..], "?secret=");
|
||||
|
||||
const name = it.next();
|
||||
const secret = it.next();
|
||||
const empty = it.next();
|
||||
|
||||
if (name != null and secret != null and empty == null) {
|
||||
return OtpAuthUrl{ .name = name.?, .secretEncoded = secret.?, .url = url };
|
||||
} else {
|
||||
return ArgumentError.InvalidOtpAuthUrl;
|
||||
if (uri.query == null) {
|
||||
return error.InvalidOtpAuthUrl;
|
||||
}
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var name = try uri.path.toRawMaybeAlloc(allocator);
|
||||
name = std.mem.trimLeft(u8, name, "/");
|
||||
|
||||
const params = try parseQueryString(allocator, try uri.query.?.toRawMaybeAlloc(allocator));
|
||||
|
||||
return OtpAuthUrl{
|
||||
.name = name,
|
||||
.secretEncoded = params.get("secret").?,
|
||||
.url = url,
|
||||
.period = if (params.get("period") != null) try std.fmt.parseInt(u32, params.get("period").?, 10) else 30,
|
||||
.digits = if (params.get("digits") != null) try std.fmt.parseInt(u4, params.get("digits").?, 10) else 6,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseQueryString(allocator: Allocator, query: []const u8) !std.StringHashMap([]const u8) {
|
||||
var map = std.StringHashMap([]const u8).init(allocator);
|
||||
|
||||
var it = std.mem.splitSequence(u8, query, "&");
|
||||
|
||||
while (it.peek() != null) {
|
||||
const next = it.next().?;
|
||||
|
||||
var paramIt = std.mem.splitSequence(u8, next, "=");
|
||||
const name = paramIt.next();
|
||||
const value = paramIt.next();
|
||||
|
||||
if (name) |n| {
|
||||
if (value) |v| {
|
||||
try map.put(n, v);
|
||||
} else {
|
||||
try map.put(n, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
fn configLocation(allocator: Allocator) ![]const u8 {
|
||||
@@ -153,6 +214,18 @@ fn executeGetList(allocator: Allocator, config_location: []const u8) !std.ArrayL
|
||||
return names;
|
||||
}
|
||||
|
||||
fn getAuthenticator(allocator: Allocator, name: []const u8, config_location: []const u8) !Authenticator {
|
||||
const otpAuthUrls = try read_config(allocator, config_location);
|
||||
|
||||
for (0..otpAuthUrls.items.len) |i| {
|
||||
const url: OtpAuthUrl = otpAuthUrls.items[i];
|
||||
if (eql(u8, url.name, name)) {
|
||||
return Authenticator{ .url = url };
|
||||
}
|
||||
}
|
||||
return error.AuthenticatorNotFound;
|
||||
}
|
||||
|
||||
test "parse command line parameter: 'list'" {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
const allocator = gpa.allocator();
|
||||
@@ -180,3 +253,12 @@ test "read list of entries" {
|
||||
try std.testing.expectEqualStrings("token2", list.items[1]);
|
||||
try std.testing.expectEqual(2, list.items.len);
|
||||
}
|
||||
|
||||
test "parse oth pauth url" {
|
||||
const actual: OtpAuthUrl = try parseOtpAuthUrl("otpauth://totp/foo?secret=MFQQ&period=31&digits=7");
|
||||
|
||||
try std.testing.expectEqualStrings("MFQQ", actual.secretEncoded);
|
||||
try std.testing.expectEqualStrings("foo", actual.name);
|
||||
try std.testing.expectEqual(31, actual.period);
|
||||
try std.testing.expectEqual(7, actual.digits);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user