LDAP OTP Authentication

Purpose

Use an Active Directory or other LDAP server as source of userid/password authentication, but add Multifactor Authenticating using either SMS/Text messages with OTP codes, Email with OTP or TOTP authenticator

Forced password change for expired/initial passwords is also supported.

Features

  • OTP generation, sending and validation
  • TOTP registration, protected by either SMS or Email OTP
  • Save shared secrets in LDAP encrypted by customized AES key
  • Password change

Overview

The Email Authentication plugin has the following classname; dk.itp.security.authentication.ldapotp.LdapOTPAuthenticationPlugin - it uses Active Directory or other LDAP server as user repository.

Configuration

Please refer to the section here: LDAP Authentication Properties for details about the configuration properties.

Dynamic Email Message Content

Both the registration email and password email can contain the following strings, which are replaced with the dynamic information before being transmitted:

  • {username}
    Replaced with the users name
  • {userid}
    Replaced with the userid
  • {code}
    Replaced with the generated registration code
  • \n
    Replaced with linefeed

Using the Email Authentication Plugin

Login / Verify Code

Simply do a regular login to authenticate the user

Agent.getInstance().login( getSessionId(), AuthTypes.AUTHTYPE_LDAPOTP, userid, registration_code);

If the call completes without errors, the code was correct.

Change Password

To change the password, after authenticating using this plugin, call 

Agent.getInstance().changePassword( getSessionId(), "oldpassword", "newpassword" );

Old password needs to match current, but can be initial or expired

Generate OTP email

To send a new One-Time-PIN email, call:

Agent.getInstance().newToken( getSessionId(), AuthTypes.AUTHTYPE_LDAPOTP, "sendemail");

Before calling this, login with userid and password using the userid/password authentication plugin AuthTypes.AUTHTYPE_LDAPOTP

The OTP will be stored in the session, and is available for use when logging in.

Verify Email OTP

Verify OTP code previously sent via email

Agent.getInstance().login( getSessionId(), AuthTypes.AUTHTYPE_LDAPOTP, userid, new String[] {"email", otpcode});

This will verify the code against the earlier sent one.

Verify SMS OTP

Verify OTP code previously sent via email

Agent.getInstance().login( getSessionId(), AuthTypes.AUTHTYPE_LDAPOTP, userid, new String[] {"sms", otpcode});

This will verify the code against the earlier sent one.


CeptorAuthenticate LDAP API

Normally this plugin is used with the HTML/javascript frontend available within the /authsms folder of Ceptor installation used from within the Gateway.

The following API definition contains both the implementation and the OpenAPI Specification for the API - it is also included as one of the default APIs

{
  "implementation": {"Sandbox": {
    "": {"*": {
      "proxy": {},
      "script": "var response = {\n  text: 'Sorry, this API operation has not yet been implemented.'\n};\ncontext.respond(200, 'OK', 'application/json', JSON.stringify(response));",
      "pipeline": {},
      "type": "script"
    }},
    "/hello": {"get": {
      "proxy": {},
      "script": "var response = {\n\t\"text\": \"a string\"\n};\ncontext.respond(200, 'OK', 'application/json', JSON.stringify(response));\n",
      "pipeline": {},
      "type": "script"
    }},
    "/auth/uidpw": {"post": {
      "proxy": {},
      "script": "%{script}function auth() {\n    var req = JSON.parse(context.getRequestBodyAsString());\n    \n    context.trace.trace(\"Authenticate called with userid: \" + req.userid);\n\n    try {\n        var pSessionId = context.id;\n        \n        context.agent.setStateVariable(pSessionId, \"require_otp\", \"true\", false);\n        context.agent[\"logon(java.lang.String, int, java.lang.String, java.lang.Object)\"](pSessionId, 59, req.userid, req.credentials);\n        \n        var isAuthenticated = context.agent.isLoggedOn(pSessionId);\n        \n        var response = {\n            // Is delayed logoff or not\n            \"is_delayed_logoff\": context.agent.isDelayedLogoff(pSessionId),\n        \t// User ID\n        \t\"user_id\": context.agent.getUser(pSessionId),\n        \t// Customer ID\n        \t\"customer_id\": isAuthenticated ? context.agent.getCustomerID(pSessionId) : null,\n        \t// Agreement ID\n        \t\"agreement_id\": isAuthenticated ? context.agent.getAgreementID(pSessionId) : null,\n        \t// Is internal user\n        \t\"internal\": isAuthenticated ? context.agent.isInternalUser(pSessionId) : null,\n        \t// Is logged on\n        \t\"logged_on\": isAuthenticated,\n        \t// Users real name\n        \t\"user_name\": context.agent.getUserName(pSessionId),\n        \t// IP address that created the session\n        \t\"ip\": context.agent.getUserIP(pSessionId),\n        \t// Authentication level\n        \t\"authentication_level\": context.agent.getAuthenticationLevel(pSessionId),\n        \t// Authentication method\n        \t\"authentication_method\": context.agent.getAuthenticationMethod(pSessionId),\n        \t// Array of users groups\n        \t\"groups\": null,\n        \t// Name of creating agent\n        \t\"creating_agent\": context.agent.getNameOfCreatingAgent(pSessionId),\n        \t// Ticket or token\n        \t\"ticket\": context.agent.getTicketFromSession(pSessionId),\n        \t// Available OTP methods\n        \t\"otpmethods\": []\n        };\n        \n        try {\n            response.groups = Java.from(context.agent.getUserGroups(pSessionId));\n        } catch(e) {}\n        \n        var otpMethods = context.agent.getStateVariable(pSessionId, \"otpmethods\");\n        if (otpMethods) {\n            response.otpmethods = otpMethods.split(\";\");\n        }\n\n        context.respond(200, 'OK', 'application/json', JSON.stringify(response));\n    } catch(err) {\n        if (! (err instanceof Java.type('java.lang.Throwable')))\n            throw err;\n            \n        context.trace.trace(\"Authentication failed\", err);\n        if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n            var response = {\n                \"code\": err.getErrorCode(),\n                \"message\": err.getErrorText()\n            };\n            \n            if (response.code == 15) { // Need OTP\n                var otpmethods = context.agent.getStateVariable(pSessionId, \"otpmethods\");\n                if (otpmethods)\n                    response.otpmethods = otpmethods.split(';');\n            }\n            var newPasswordRequired = context.agent.getStateVariable(pSessionId, \"ldap.newpasswordrequired\");\n            if (newPasswordRequired)\n                response.newpasswordrequired = \"true\" == newPasswordRequired;\n                \n            if (!response.message)\n                response.message = \"Unknown error\";\n                \n            context.respond(401, 'Authentication Failed', 'application/json', JSON.stringify(response));\n        } else {\n            throw err;\n        }\n    }\n}\n\nauth();\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/info": {"get": {
      "proxy": {},
      "script": "%{script}var pSessionId = context.id;\n\nvar isAuthenticated = context.agent.isLoggedOn( pSessionId );\n\nvar response = {\n    // Is delayed logoff or not\n    \"is_delayed_logoff\": context.agent.isDelayedLogoff(pSessionId),\n\t// User ID\n\t\"user_id\": context.agent.getUser(pSessionId),\n\t// Customer ID\n\t\"customer_id\": isAuthenticated ? context.agent.getCustomerID(pSessionId) : null,\n\t// Agreement ID\n\t\"agreement_id\": isAuthenticated ? context.agent.getAgreementID(pSessionId) : null,\n\t// Is internal user\n\t\"internal\": isAuthenticated ? context.agent.isInternalUser(pSessionId) : null,\n\t// Is logged on\n\t\"logged_on\": isAuthenticated,\n\t// Users real name\n\t\"user_name\": context.agent.getUserName(pSessionId),\n\t// IP address that created the session\n\t\"ip\": context.agent.getUserIP(pSessionId),\n\t// Authentication level\n\t\"authentication_level\": context.agent.getAuthenticationLevel(pSessionId),\n\t// Authentication method\n\t\"authentication_method\": context.agent.getAuthenticationMethod(pSessionId),\n\t// Array of users groups\n\t\"groups\": null,\n\t// Name of creating agent\n\t\"creating_agent\": context.agent.getNameOfCreatingAgent(pSessionId),\n\t// Ticket or token\n\t\"ticket\": context.agent.getTicketFromSession(pSessionId)\n};\n\ntry {\n    response.groups = Java.from(context.agent.getUserGroups(pSessionId));\n} catch(e) {}\n\ncontext.respond(200, 'OK', 'application/json', JSON.stringify(response));\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/auth/logoff": {"put": {
      "proxy": {},
      "script": "%{script}context.agent.logoff(context.id);\ncontext.respond(201, 'OK', 'application/json', '');\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/auth/sendsms": {"put": {
      "proxy": {},
      "script": "%{script}try {\n    context.agent.newToken(context.id,59,\"sendsms\");\n    \n    context.respond(201, 'SMS Sent', 'application/json','');\n} catch(err) {\n    if (! (err instanceof Java.type('java.lang.Throwable')))\n        throw err;\n        \n    context.trace.trace(\"SendSMS failed\", err);\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n        var response = {\n            \"code\": err.getErrorCode(),\n            \"message\": err.getErrorText()\n        };\n        \n        if (!response.message)\n            response.message = \"Unknown error\";\n            \n        context.respond(400, 'Send SMS Failed', 'application/json', JSON.stringify(response));    \n    } else {\n        throw err;\n    }\n}\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/auth/verifysmsotp": {"post": {
      "proxy": {},
      "script": "%{script}try {\n    var credentials = [\"sms\",pCode];\n    \n    context.agent.logon(context.id,59,context.agent.getUser(context.id), Java.to(credentials, \"java.lang.String[]\"));\n    \n    context.respond(201, 'OK', 'application/json', '');\n} catch(err) {\n    if (! (err instanceof Java.type('java.lang.Throwable')))\n        throw err;\n        \n    context.trace.trace(\"VerifySMS failed\", err);\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n        var response = {\n            \"code\": err.getErrorCode(),\n            \"message\": err.getErrorText()\n        };\n        \n        if (!response.message)\n            response.message = \"Unknown error\";\n            \n        context.respond(400, 'Verify SMS OTP Failed', 'application/json', JSON.stringify(response));    \n    } else {\n        throw err;\n    }\n}\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/auth/verifytotp": {"post": {
      "proxy": {},
      "script": "%{script}try {\n    var credentials = [\"totp\",pCode];\n    \n    context.agent.logon(context.id,59,context.agent.getUser(context.id), Java.to(credentials, \"java.lang.String[]\"));\n    \n    context.respond(201, 'OK', 'application/json', '');\n} catch(err) {\n    if (! (err instanceof Java.type('java.lang.Throwable')))\n        throw err;\n        \n    context.trace.trace(\"Verify TOTP failed\", err);\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n        var response = {\n            \"code\": err.getErrorCode(),\n            \"message\": err.getErrorText()\n        };\n        \n        if (!response.message)\n            response.message = \"Unknown error\";\n            \n        context.respond(400, 'Verify TOTP Failed', 'application/json', JSON.stringify(response));    \n    } else {\n        throw err;\n    }\n}\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/auth/verifyemailotp": {"post": {
      "proxy": {},
      "script": "%{script}try {\n    var credentials = [\"email\",pCode];\n    \n    context.agent.logon(context.id,59,context.agent.getUser(context.id), Java.to(credentials, \"java.lang.String[]\"));\n    \n    context.respond(201, 'OK', 'application/json', '');\n} catch(err) {\n    if (! (err instanceof Java.type('java.lang.Throwable')))\n        throw err;\n        \n    context.trace.trace(\"Verify email OTP failed\", err);\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n        var response = {\n            \"code\": err.getErrorCode(),\n            \"message\": err.getErrorText()\n        };\n        \n        if (!response.message)\n            response.message = \"Unknown error\";\n            \n        context.respond(400, 'Verify email OTP Failed', 'application/json', JSON.stringify(response));    \n    } else {\n        throw err;\n    }\n}\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/auth/sendemail": {"put": {
      "proxy": {},
      "script": "%{script}try {\n    context.agent.newToken(context.id,59,\"sendemail\");\n    \n    context.respond(201, 'Email Sent', 'application/json','');\n} catch(err) {\n    if (! (err instanceof Java.type('java.lang.Throwable')))\n        throw err;\n        \n    context.trace.trace(\"Send Email failed\", err);\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n        var response = {\n            \"code\": err.getErrorCode(),\n            \"message\": err.getErrorText()\n        };\n        \n        if (!response.message)\n            response.message = \"Unknown error\";\n            \n        context.respond(400, 'Send Email Failed', 'application/json', JSON.stringify(response));    \n    } else {\n        throw err;\n    }\n}\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/resetpassword": {"post": {
      "proxy": {},
      "script": "%{script}var body = context.getRequestBodyAsString();\n\ntry {\n    context.agent.executeAuthpluginCommand(context.id, 9 , 'resetpassword', body);\n    \n    context.respond(201, 'Password Reset', 'application/json', '');\n} catch(err) {\n    if (! (err instanceof Java.type('java.lang.Throwable')))\n        throw err;\n        \n    context.trace.trace(\"Password reset failed\", err);\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n        var response = {\n            \"code\": err.getErrorCode(),\n            \"message\": err.getErrorText()\n        };\n        \n        if (!response.message)\n            response.message = \"Unknown error\";\n        \n        context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response));    \n    } else {\n        throw err;\n    }\n}\n",
      "pipeline": {},
      "type": "script",
      "override": false
    }},
    "/initiateresetpassword": {"post": {
      "proxy": {},
      "script": "%{script}var body = context.getRequestBodyAsString();\n\ntry {\n    context.agent.executeAuthpluginCommand(context.id, 9 , 'initresetpassword', body);\n    \n    context.respond(201, 'Password Reset', 'application/json', '');\n} catch(err) {\n    if (! (err instanceof Java.type('java.lang.Throwable')))\n        throw err;\n        \n    context.trace.trace(\"Password reset failed\", err);\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n        var response = {\n            \"code\": err.getErrorCode(),\n            \"message\": err.getErrorText()\n        };\n        \n        if (!response.message)\n            response.message = \"Unknown error\";\n        \n        context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response));    \n    } else {\n        throw err;\n    }\n}\n",
      "pipeline": {},
      "type": "script",
      "override": false
    }},
    "/resetpasswordotp": {"post": {
      "proxy": {},
      "script": "%{script}var body = context.getRequestBodyAsString();\n\ntry {\n    context.agent.executeAuthpluginCommand(context.id, 9 , 'resetpasswordotp', body);\n    \n    context.respond(201, 'Password Reset', 'application/json', '');\n} catch(err) {\n    if (! (err instanceof Java.type('java.lang.Throwable')))\n        throw err;\n        \n    context.trace.trace(\"Password reset failed\", err);\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n        var response = {\n            \"code\": err.getErrorCode(),\n            \"message\": err.getErrorText()\n        };\n        \n        if (!response.message)\n            response.message = \"Unknown error\";\n        \n        context.respond(400, 'Password reset failed', 'application/json', JSON.stringify(response));    \n    } else {\n        throw err;\n    }\n}\n",
      "pipeline": {},
      "type": "script",
      "override": false
    }},
    "/register": {"post": {
      "proxy": {},
      "script": "%{script}function register() {\n    var req = JSON.parse(context.getRequestBodyAsString());\n    \n    context.trace.trace(\"Register called with userid: \" + req.userid);\n\n    try {\n        var pSessionId = context.id;\n        var CreateUserCredentials = Java.type(\"dk.itp.security.passticket.CreateUserCredentials\");\n        var cc = new CreateUserCredentials();\n        cc.userid = req.userid;\n        cc.password = req.password;\n        cc.firstname = req.firstname;\n        cc.lastname = req.lastname;\n        cc.email = req.email;\n        cc.mobile = req.mobile;\n        \n        context.agent.logon(pSessionId, 9, req.userid, cc);\n        \n        context.respond(201, 'Registration successful', 'application/json', '');\n    } catch(err) {\n        if (! (err instanceof Java.type('java.lang.Throwable')))\n            throw err;\n            \n        context.trace.trace(\"Registration failed\", err);\n        if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n            var response = {\n                \"code\": err.getErrorCode(),\n                \"message\": err.getErrorText()\n            };\n            \n            if (!response.message)\n                response.message = \"Unknown error\";\n                \n            context.respond(401, 'Registration Failed', 'application/json', JSON.stringify(response));\n        } else {\n            throw err;\n        }\n    }\n}\n\nregister();\n",
      "pipeline": {},
      "type": "script",
      "override": false
    }},
    "/totp/qr": {"post": {
      "proxy": {},
      "script": "%{script}try {\r\n    if (!context.agent.isLoggedOn(context.id)) {\r\n        var response = {\r\n            \"code\": 18, // AuthErrorCodes.ERROR_NOTAUTHENTICATED\r\n            \"message\": \"Requires authentication\"\r\n        };\r\n        \r\n        context.respond(403, 'Denied not logged on', 'application/json', JSON.stringify(response));\r\n    } else {\r\n        var body = JSON.parse(context.getRequestBodyAsString());\r\n\r\n        var response = {\r\n        \t// QR Code in PNG format\r\n        \t// Format: base64\r\n        \t// Secret value to register\r\n        \t\"secret\": context.agent.newToken(context.id,59, \"secretpassword;\"+body.password),\r\n        \t\"qr\": context.agent.newToken(context.id,59, \"PNG\")\r\n        };\r\n        context.respond(200, 'OK', 'application/json', JSON.stringify(response));\r\n    }\r\n} catch(err) {\r\n    if (! (err instanceof Java.type('java.lang.Throwable')))\r\n        throw err;\r\n        \r\n    context.trace.trace(\"QR Code registration failed\", err);\r\n    if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\r\n        var response = {\r\n            \"code\": err.getErrorCode(),\r\n            \"message\": err.getErrorText()\r\n        };\r\n        \r\n        if (!response.message)\r\n            response.message = \"Unknown error\";\r\n            \r\n        context.respond(400, 'QR Code registration failed', 'application/json', JSON.stringify(response));    \r\n    } else {\r\n        throw err;\r\n    }\r\n}\r\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }},
    "/auth/changepw": {"post": {
      "proxy": {},
      "script": "%{script}function auth() {\n    var req = JSON.parse(context.getRequestBodyAsString());\n\n    try {\n        if (!context.agent.getUser(context.id)) {\n            var response = {\n                \"code\": -1,\n                \"message\": \"Missing previous login, or timeout - please relogin\"\n            };\n            context.respond(400, 'OK', 'application/json', JSON.stringify(response));\n            return;\n        }\n// Changing password is not allowed from restricted agent using regular command - but LDAPOTP plugin allows this alternate method\n// context.agent.changePassword(context.id, req.password, req.newpassword);\n\n        var input = [context.agent.getUser(context.id), req.password, req.newpassword];\n        context.agent.executeAuthpluginCommand(context.id,59,\"changepassword\", Java.to(input));\n        \n        // Force user to login again\n        context.agent.logoff(context.id);\n        \n        context.respond(201, 'OK', 'application/json', JSON.stringify(response));\n    } catch(err) {\n        if (! (err instanceof Java.type('java.lang.Throwable')))\n            throw err;\n            \n        context.trace.trace(\"Authentication failed\", err);\n        if (err instanceof Java.type('dk.itp.security.passticket.PTException')) {\n            var response = {\n                \"code\": err.getErrorCode(),\n                \"message\": err.getErrorText()\n            };\n            \n            if (!response.message)\n                response.message = \"Unknown error\";\n                \n            context.respond(400, 'Change password failed', 'application/json', JSON.stringify(response));\n        } else {\n            throw err;\n        }\n    }\n}\n\nauth();\n",
      "pipeline": {},
      "type": "script",
      "override": true
    }}
  }},
  "security": {
    "authorization": {
      "noauthorization.for.options": false,
      "roles": []
    },
    "authentication": {
      "apikey": false,
      "apikey.headername": "ceptor-apikey",
      "basicauth": false,
      "clientcert": false,
      "bearer": false,
      "oauth2": false,
      "openidconnect": false,
      "oauth2.scopes": [],
      "advanced": {}
    }
  },
  "id": "7bcd6bc8-2818-4fdb-b8e6-a0e33745f7b9",
  "apiid": "d06015d1-b737-41e0-b164-02a6c123ac3c",
  "name": "v1",
  "basepath": "/ceptorauthenticateldap/1/",
  "apitype": "openapi",
  "cors": true,
  "private": false,
  "openapispec": {
    "openapi": "3.0.0",
    "info": {
      "version": "1.0.0",
      "title": "Ceptauthenticate"
    },
    "paths": {
      "/info": {"get": {
        "summary": "Gets info about currently authenticated user/session",
        "operationId": "info",
        "responses": {
          "200": {
            "description": "Session Info",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Session"}}}
          },
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        }
      }},
      "/auth/uidpw": {"post": {
        "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/AuthenticateRequest"}}}},
        "summary": "Authenticate using userid/password",
        "responses": {
          "201": {
            "description": "Authentication successful",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Session"}}}
          },
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        },
        "description": "Authenticate a user using userid and password",
        "operationId": "authuidpw",
        "deprecated": false,
        "tags": []
      }},
      "/auth/changepw": {"post": {
        "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/ChangePasswordRequest"}}}},
        "summary": "Change password",
        "responses": {
          "201": {"description": "Authentication successful"},
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        },
        "description": "Change password, requires that login with old password is called first, or this call will fail",
        "operationId": "authchangepw",
        "deprecated": false,
        "tags": []
      }},
      "/auth/logoff": {"put": {
        "summary": "Logoff",
        "description": "Logoff the user",
        "operationId": "logoff",
        "responses": {
          "201": {"description": "Logoff completed"},
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        }
      }},
      "/auth/sendsms": {"put": {
        "summary": "Send SMS",
        "description": "Send SMS to the user",
        "operationId": "sendsms",
        "responses": {
          "201": {"description": "SMS sent"},
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        }
      }},
      "/auth/verifysmsotp": {"post": {
        "summary": "Verify SMS OTP",
        "description": "Verifies OTP code earlier sent as SMS",
        "operationId": "verifysms",
        "responses": {
          "201": {"description": "OK"},
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        },
        "parameters": [{
          "name": "code",
          "description": "Entered OTP code",
          "in": "query",
          "required": true,
          "allowEmptyValue": false,
          "deprecated": false,
          "schema": {"type": "string"}
        }]
      }},
      "/auth/sendemail": {"put": {
        "summary": "Send Email",
        "description": "Send Email with OTP to the user",
        "operationId": "sendemail",
        "responses": {
          "201": {"description": "Email sent"},
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        }
      }},
      "/auth/verifyemailotp": {"post": {
        "summary": "Verify Email OTP",
        "description": "Verifies OTP code earlier sent as Email",
        "operationId": "verifyemail",
        "responses": {
          "201": {"description": "OK"},
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        },
        "parameters": [{
          "name": "code",
          "description": "Entered OTP code",
          "in": "query",
          "required": true,
          "allowEmptyValue": false,
          "deprecated": false,
          "schema": {"type": "string"}
        }]
      }},
      "/auth/verifytotp": {"post": {
        "summary": "Verify TOTP",
        "description": "Verifies TOTP code (Google/MS etc. Authenticator App)",
        "operationId": "verifytotp",
        "responses": {
          "201": {"description": "OK"},
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        },
        "parameters": [{
          "name": "code",
          "description": "Entered OTP code",
          "in": "query",
          "required": true,
          "allowEmptyValue": false,
          "deprecated": false,
          "schema": {"type": "string"}
        }]
      }},
      "/totp/qr": {"post": {
        "summary": "Generate TOTP secret and QR code",
        "operationId": "totpqr",
        "responses": {
          "200": {
            "description": "TOTP QR code",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TOTPQRResponse"}}}
          },
          "default": {
            "description": "unexpected error",
            "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}
          }
        },
        "requestBody": {"content": {"application/json": {"schema": {"$ref": "#/components/schemas/TOTPQRRequest"}}}}
      }}
    },
    "components": {"schemas": {
      "AuthenticateRequest": {
        "required": ["userid"],
        "properties": {
          "userid": {"type": "string"},
          "credentials": {"type": "string"}
        }
      },
      "Session": {"properties": {
        "is_delayed_logoff": {
          "type": "boolean",
          "description": "True, if delayed logoff is initiated for this session"
        },
        "user_id": {
          "type": "string",
          "description": "User ID"
        },
        "customer_id": {
          "type": "string",
          "description": "Customer ID"
        },
        "agreement_id": {
          "type": "string",
          "description": "Agreement ID"
        },
        "logged_on": {
          "type": "boolean",
          "description": "Indicates if a user is authenticated in this session"
        },
        "internal": {
          "type": "boolean",
          "description": "Is this user an internal user or not"
        },
        "user_name": {
          "type": "string",
          "description": "Users real name"
        },
        "ip": {
          "type": "string",
          "description": "IP address that created the session"
        },
        "groups": {
          "type": "array",
          "description": "Array of users groups",
          "items": {
            "type": "string",
            "description": "Group/Role name"
          }
        },
        "authentication_method": {
          "type": "integer",
          "description": "Authentication method identifier"
        },
        "authentication_level": {
          "type": "integer",
          "description": "Authentication level - the highger, the more secure"
        },
        "creating_agent": {
          "type": "string",
          "description": "Name of agent that created this session"
        },
        "ticket": {
          "type": "string",
          "description": "Optional ticket for this session - can be JWT token, SAML token, SSL clientcert or anything else used as an alternate key to this session instead of the session ID"
        }
      }},
      "ChangePasswordRequest": {
        "description": "Change password - login must have been called first with user/password or this call will fail",
        "required": [
          "password",
          "newpassword"
        ],
        "properties": {
          "password": {
            "description": "Old password",
            "type": "string"
          },
          "newpassword": {
            "description": "New password",
            "type": "string"
          }
        }
      },
      "TOTPQRRequest": {
        "required": ["password"],
        "properties": {"password": {
          "type": "string",
          "format": "password",
          "description": "User password"
        }}
      },
      "TOTPQRResponse": {
        "required": [
          "qr",
          "secret"
        ],
        "properties": {
          "qr": {
            "type": "string",
            "format": "base64",
            "description": "QR Code in PNG format"
          },
          "secret": {
            "type": "string",
            "description": "Secret value to register"
          }
        }
      },
      "Error": {
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32"
          },
          "message": {"type": "string"}
        }
      }
    }},
    "security": [],
    "servers": [{
      "url": "https://localhost:8443/ceptorauthenticateldap/1",
      "description": "Sandbox environment, used for initial testing of APIs, playing around with new versions"
    }]
  },
  "tags": [],
  "requestmodification": {
    "session.needed": false,
    "cookiesnapper": {},
    "plugin": {}
  },
  "override.apiprofile.security": true,
  "remote.openapispec.loadfromdestination": false,
  "deprecated": false,
  "documentation": "",
  "deployed": ["Sandbox"]
}



© Ceptor ApS. All Rights Reserved.