{"record":{"id":"592031fb49b099ba","repo":"beemdevelopment/Aegis","slug":"pin-must-be-set-before-generating-an-otp","errorCode":null,"errorMessage":"PIN must be set before generating an OTP","messagePattern":"PIN must be set before generating an OTP","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/main/java/com/beemdevelopment/aegis/otp/MotpInfo.java","lineNumber":36,"sourceCode":"\n    public static final int PERIOD = 10;\n    public static final int DIGITS = 6;\n\n    private String _pin;\n\n    public MotpInfo(@NonNull byte[] secret) throws OtpInfoException {\n        this(secret, null);\n    }\n\n    public MotpInfo(byte[] secret, String pin) throws OtpInfoException {\n        super(secret, ALGORITHM, DIGITS, PERIOD);\n        setPin(pin);\n    }\n\n    @Override\n    public String getOtp(long time) {\n        if (_pin == null) {\n            throw new IllegalStateException(\"PIN must be set before generating an OTP\");\n        }\n\n        try {\n            MOTP otp = MOTP.generateOTP(getSecret(), getAlgorithm(false), getDigits(), getPeriod(), getPin(), time);\n            return otp.toString();\n        } catch (NoSuchAlgorithmException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    @Override\n    public String getTypeId() {\n        return ID;\n    }\n\n    @Override\n    public JSONObject toJson() {\n        JSONObject result = super.toJson();","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/beemdevelopment/Aegis/blob/d6f4e5925a97e4e91593f1542085eae03432a759/app/src/main/java/com/beemdevelopment/aegis/otp/MotpInfo.java#L18-L54","documentation":"MotpInfo.getOtp requires a PIN to have been set (via a MotpInfo constructor or setPin) before it can generate an mOTP code. If _pin is still null it throws IllegalStateException, because the mOTP algorithm cannot produce a code without the PIN component.","triggerScenarios":"Calling getOtp(time) on a MotpInfo instance that was constructed without a PIN and whose setPin was never invoked — i.e. generating a code for an mOTP entry whose PIN was never initialized (only seen in tests like testMotpInfoOtp that build MotpInfo directly).","commonSituations":"Programmatic use of MotpInfo where the developer builds the object incrementally and calls getOtp before setPin; deserialization paths that skip PIN assignment; partially constructed entries.","solutions":["Call setPin(...) (or use the constructor that takes a pin) before invoking getOtp.","Guard with a null check: only call getOtp when the entry's PIN is configured.","If the entry legitimately has no PIN, it is not a valid mOTP credential — fix the source data/import."],"exampleFix":"// before\nMotpInfo motp = new MotpInfo(secret, algorithm, digits, period);\nString code = motp.getOtp(time); // throws\n\n// after\nMotpInfo motp = new MotpInfo(secret, algorithm, digits, period);\nmotp.setPin(\"1234\");\nString code = motp.getOtp(time);","handlingStrategy":"validation","validationCode":"if (motpInfo.getPin() == null) {\n    throw new IllegalStateException(\"configure the mOTP PIN before generating codes\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    String code = motpInfo.getOtp(time);\n} catch (IllegalStateException e) {\n    // PIN never set; prompt the user to configure the PIN\n    promptForPin();\n}","preventionTips":["Always use the MotpInfo constructor variant that takes the PIN.","Check PIN presence before calling getOtp in code that builds entries incrementally.","Ensure deserialization always restores the PIN field."],"tags":["otp","motp","illegal-state","missing-pin"],"backgroundTag":"invalid-state-transition","analyzedSha":"d6f4e5925a97e4e91593f1542085eae03432a759","analyzedAt":"2026-09-08T00:46:31.111Z","contentChangedAt":"2026-09-08T00:46:31.111Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}