{"record":{"id":"8c3b483803a74d85","repo":"BeyondDimension/SteamTools","slug":"import-only-supports-otpauth-totp-or-otpauth","errorCode":null,"errorMessage":"Import only supports otpauth://totp/ or otpauth://hotp/","messagePattern":"Import only supports otpauth://totp/ or otpauth://hotp/","errorType":"exception","errorClass":"ApplicationException","httpStatus":null,"severity":"warning","filePath":"src/BD.WTTS.Client.Plugins.Authenticator/UI/ViewModels/AuthenticatorImportPageViewModel.cs","lineNumber":272,"sourceCode":"                var qm = line.IndexOf(\"?\", StringComparison.Ordinal);\n                if (hash != -1 && hash < qm)\n                {\n                    line = $\"{line.Substring(0, hash)}%23{line[(hash + 1)..]}\";\n                }\n\n                // parse and validate URI\n                var uri = new Uri(line);\n\n                // we only support \"otpauth\"\n                if (uri.Scheme != \"otpauth\")\n                {\n                    throw new ApplicationException(\"Import only supports otpauth://\");\n                }\n\n                // we only support totp (not hotp)\n                if (uri.Host != \"totp\" && uri.Host != \"hotp\")\n                {\n                    throw new ApplicationException(\"Import only supports otpauth://totp/ or otpauth://hotp/\");\n                }\n\n                // get the label and optional issuer\n                string issuer = string.Empty;\n                string label = string.IsNullOrEmpty(uri.LocalPath) == false\n                    ? uri.LocalPath[1..]\n                    : string.Empty; // skip past initial /\n                int p = label.IndexOf(\":\", StringComparison.Ordinal);\n                if (p != -1)\n                {\n                    issuer = label.Substring(0, p);\n                    label = label[(p + 1)..];\n                }\n\n                // + aren't decoded\n                label = label.Replace(\"+\", \" \");\n\n                var query = HttpUtility.ParseQueryString(uri.Query);","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/BeyondDimension/SteamTools/blob/c16ffa08e03b192d23ada290c4969e77f9201f3d/src/BD.WTTS.Client.Plugins.Authenticator/UI/ViewModels/AuthenticatorImportPageViewModel.cs#L254-L290","documentation":"Thrown during authenticator import when the URI scheme is otpauth but the host (the OTP type segment) is neither 'totp' nor 'hotp'. The importer supports only time-based and HMAC-based counter one-time passwords; any other type label in otpauth://<type>/ is rejected.","triggerScenarios":"Parsing otpauth:// URIs where the authority segment is something other than totp or hotp — e.g. otpauth://steam/..., a custom type, or a malformed URI whose 'host' is empty.","commonSituations":"Steam-style URIs using a non-standard type label; entries exported by a tool that invented its own type; URI was truncated so the type segment is missing; case/encoding issues that shifted the host.","solutions":["Re-export the entry as otpauth://totp/ or otpauth://hotp/ from the source authenticator.","For Steam entries, import via the dedicated Steam Guard import flow instead of the otpauth parser.","Manually correct the type segment to 'totp' (most secrets are TOTP-compatible).","Reject the entry in the UI with a clear message listing the supported types."],"exampleFix":"// before\nif (uri.Host != \"totp\" && uri.Host != \"hotp\")\n    throw new ApplicationException(\"Import only supports otpauth://totp/ or otpauth://hotp/\");\n\n// after: tolerant, case-insensitive check with guidance\nvar type = uri.Host.ToLowerInvariant();\nif (type != \"totp\" && type != \"hotp\")\n    throw new ArgumentException(string.Format(Strings.Import_UnsupportedOtpType, uri.Host));","handlingStrategy":"validation","validationCode":"static bool IsSupportedOtpType(Uri uri)\n    => uri.Host.Equals(\"totp\", StringComparison.OrdinalIgnoreCase)\n    || uri.Host.Equals(\"hotp\", StringComparison.OrdinalIgnoreCase);\n\nif (!IsSupportedOtpType(uri))\n    errors.Add(($\"Line {n}: otpauth type '{uri.Host}' not supported (use totp or hotp).\", line));","typeGuard":"bool IsTotpOrHotp(Uri u) => u.Host is \"totp\" or \"hotp\";","tryCatchPattern":"try { ParseAuthenticator(line); }\ncatch (ApplicationException ex) when (ex.Message.Contains(\"otpauth://totp/ or otpauth://hotp/\"))\n{\n    importErrors.Add(($\"Unsupported OTP type (line {n}).\", line));\n    continue;\n}","preventionTips":["Validate the type segment case-insensitively and surface the allowed set to the user.","Route Steam-style entries to the dedicated Steam Guard importer.","Show a per-line error list during bulk import instead of aborting on the first bad entry.","Normalize common type aliases before rejecting (e.g. 'totp' typos)."],"tags":["authenticator","import","validation","totp","hotp"],"backgroundTag":null,"analyzedSha":"c16ffa08e03b192d23ada290c4969e77f9201f3d","analyzedAt":"2026-08-13T11:52:20.410Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}