users.bas at [7348021f6d]

File modules/users.bas artifact 45273e6afa part of check-in 7348021f6d


'Users module file

#INCLUDE "modules.bi"

CONST AS STRING ModuleName = "users"
DIM SHARED ModulePath AS STRING
ModulePath = "modules" + PATH_DELIMITER + ModuleName + PATH_DELIMITER

'ArrayIndex = UBOUND(CompiledModules) + 1
'REDIM PRESERVE CompiledModules(ArrayIndex)
'CompiledModules(ArrayIndex) = "users"

DIM SHARED AS STRING SessionContent, UserData
CONST AS STRING SessionName = "iguana-token"

'Load the session content into a variable
SessionContent = ReadFile(TempPath + "sessions" + PATH_DELIMITER + SHA256(GetCookie(SessionName)) + ".txt")

FUNCTION ReadSession(Key AS STRING) AS STRING
  DIM Result AS STRING
  IF SessionContent <> "" THEN
    Result = SplitVar(SessionContent, Key, LINE_ENDING)
  END IF
  IF Result = "" THEN
    Result = Key
  END IF
  ReadSession = Result
  'Clean memory
  Result = ""
END FUNCTION

'Load the user file content into a variable
UserData = ReadFile(DataPath + "users" + PATH_DELIMITER + ReadSession("session_userfile"))

FUNCTION IsAuth() AS SHORT
  IF SHA256(GetCookie(SessionName)) = ReadSession("session_token") THEN
    IsAuth = -1
  ELSE
    IsAuth = 0
  END IF
END FUNCTION

SUB LoadUsersInterface()
  DIM AS STRING Action, FileContent, TempContent, SessionFile, Result
  DIM AS STRING StoredPassword, Salt, NewSalt, UserAlias, UserEmail, UserToken, UserFile
  DIM AS SHORT SaltLen, TokenLen

  IF CINT(Settings("fancy_url")) THEN
    IF UriPart(1) <> "" THEN
      Action = UriPart(1)
    END IF
  ELSE
    IF QueryString("action") <> "" THEN
      Action = QueryString("action")
    END IF
  END IF

  IF Action <> "" THEN
    SELECT CASE Action
      CASE "login"
        SiteTitle = Language("module_users_login")
        Result = LoadTplFile(ModulePath, "login.html")
        UserAlias = ValidateChar(Post("user-alias"))
        IF UserAlias <> "" THEN
          UserFile = DataPath + "users" + PATH_DELIMITER + UserAlias + ".txt"
          FileContent = ReadFile(UserFile)
          IF FileContent <> "" THEN
            'Retrieve stored password
            StoredPassword = SplitVar(FileContent, "user_password", LINE_ENDING)
            'Get the password salt
            Salt = LEFT(StoredPassword, INSTR(StoredPassword, "+") - 1)
            IF SHA256(Salt + Post("user-password")) = MID(StoredPassword, INSTR(StoredPassword, "+") + 1) THEN
              'Password match
              IF CINT(SplitVar(FileContent, "user_status", LINE_ENDING)) THEN
                'User is active
                TokenLen = 32
                'Create random session ID
                DIM AS STRING SessionToken = RandomString(TokenLen)
                'Create session file
                TempContent = "" + _
                "session_userfile=" + LCASE(UserAlias) + ".txt" + LINE_ENDING + _
                "session_token=" + SHA256(SessionToken) + LINE_ENDING + _
                "session_ipaddr=" + ENVIRON("REMOTE_ADDR") + LINE_ENDING + _
                "session_useragent=" + ENVIRON("HTTP_USER_AGENT") + LINE_ENDING + _
                "session_logintime=" + SystemDate() + "-" + SystemTime() + LINE_ENDING + _
                "session_validuntil=" + "" + LINE_ENDING
                WriteFile(TempPath + "sessions" + PATH_DELIMITER + SHA256(SessionToken) + ".txt", TempContent)
                'Clean memory
                TempContent = ""
                'Create cookie
                IF CINT(Post("user-remember")) THEN
                  PRINT SetCookie(SessionName, SessionToken, "", "/", "", 3600 * 24 * 30, 0, 1)
                ELSE
                  PRINT SetCookie(SessionName, SessionToken, "", "/", "", 3600 * 24, 0, 1)
                END IF
                'Redirect to user panel
                PRINT "Refresh: 10;url=" + Settings("site_url") + CreateURL("module_users")
                Result = LoadTplFile(ModulePath, "logged.html")
              ELSE
                'The user is inactive or banned
                Result = Replace(Result, "error_message", Language("error_user_not_active"))
              END IF
            ELSE
              'Password does not match
              Result = Replace(Result, "error_message", Language("error_wrong_password"))
            END IF
            'Clean memory
            FileContent = ""
          ELSE
            'User file does not exists
            Result = Replace(Result, "error_message", Language("error_user_not_registered"))
          END IF
        ELSE
          IF IsAuth() THEN
            'Redirect to user panel
            PRINT "Refresh: 10;url=" + Settings("site_url") + CreateURL("module_users")
            Result = "403"
          ELSE
            Result = Replace(Result, "error_message", "")
          END IF
        END IF
      CASE "logout"
        IF IsAuth() THEN
          SessionFile = TempPath + "sessions" + PATH_DELIMITER + ReadSession("session_token") + ".txt"
          IF FileExists(SessionFile) THEN
            DeleteFile(SessionFile)
            SiteTitle = Language("module_users_control_panel")
            Result = LoadTplFile(ModulePath, "logout.html")
            'Cookie deletion
            PRINT SetCookie(SessionName, GetCookie(SessionName), "", "/", "Thu, 01 Jan 1970 00:00:01 GMT", 0, 0, 1)
            PRINT "Refresh: 10;url=" + Settings("site_url")
          ELSE
            SiteTitle = Language("module_users_login")
            Result = LoadTplFile(ModulePath, "login.html")
            Result = Replace(Result, "error_message", Language("error_session_not_exists"))
          END IF
        ELSE
          Result = "403"
        END IF
      CASE "register"
        SiteTitle = Language("module_users_register")
        Result = LoadTplFile(ModulePath, "register.html")
        IF Post("user-captcha") <> "" AND Post("captcha-number") <> "" THEN
          IF ValidateCaptcha(Post("user-captcha"), Post("captcha-number")) THEN
            UserAlias = ValidateChar(Post("user-alias"))
            UserEmail = ValidateChar(LCASE(Post("user-email")))
            IF UserAlias <> "" AND UserEmail <> "" THEN
              UserFile = DataPath + "users" + PATH_DELIMITER + LCASE(UserAlias) + ".txt"
              'Check if the user alias is already registered
              IF NOT FileExists(UserFile) THEN
                SaltLen = 8
                Salt = RandomString(SaltLen)
                TokenLen = 16
                UserToken = RandomString(TokenLen)
                FileContent = "" + _
                "user_alias=" + UserAlias + LINE_ENDING + _
                "user_password=" + Salt + "+" + SHA256(Salt + Post("user-password")) + LINE_ENDING + _
                "user_regdate=" + SystemDate() + LINE_ENDING + _
                "user_email=" + UserEmail + LINE_ENDING + _
                "user_token=" + UserToken + LINE_ENDING + _
                "user_status=0" + LINE_ENDING + _
                "user_group=0"
                '"user_lastsession="
                'Save user data
                WriteFile(UserFile, FileContent)
                'Clean memory
                FileContent = ""
                'Parse email template
                TempContent = LoadTplFile(ModulePath, "activate-user-mail.html")
                TempContent = Replace(TempContent, "user_alias", UserAlias)
                TempContent = Replace(TempContent, "user_token", UserToken)
                'Send activation link
                'SendMail(UserEmail, UserAlias, Settings("site_email"), Settings("site_title"), SiteTitle, TempContent)
                Result = LoadTplFile(ModulePath, "registered.html")
              ELSE
                Result = Replace(Result, "error_message", Language("error_user_already_registered"))
              END IF
            ELSE
              Result = Replace(Result, "error_message", Language("error_user_name_empty"))
            END IF
          ELSE
            Result = Replace(Result, "error_message", Language("error_captcha_wrong_answer"))
          END IF
        ELSE
          Result = Replace(Result, "error_message", "")
        END IF
      CASE "change-password"
        IF IsAuth() THEN
          SiteTitle = Language("module_users_change_password")
          Result = LoadTplFile(ModulePath, "change-password.html")
          SessionFile = DataPath + "users/" + ReadSession("session_userfile")
          FileContent = ReadFile(SessionFile)
          IF Post("new-user-password") <> "" AND Post("current-user-password") <> "" THEN
            'Form sent
            'Retrieve stored password
            StoredPassword = SplitVar(FileContent, "user_password", LINE_ENDING)
            'This string contains the salt + hashed password
            'Get the password salt
            Salt = LEFT(StoredPassword, INSTR(StoredPassword, "+") - 1)
            IF SHA256(Salt + Post("current-user-password")) = MID(StoredPassword, INSTR(StoredPassword, "+") + 1) THEN
              'Password match, replace old password with the new one
              'Generate new salt
              SaltLen = 8
              NewSalt = RandomString(SaltLen)
              'FIXME: Verify new password not empty
              FileContent = Replace(FileContent, StoredPassword, NewSalt + "+" + SHA256(NewSalt + Post("new-user-password")))
              WriteFile(SessionFile, FileContent)
              'Clean memory
              FileContent = ""
              Result = Replace(Result, "error_message", Language("success_password_changed"))
            ELSE
              'Password does not match
              Result = Replace(Result, "error_message", Language("error_wrong_password"))
            END IF
          ELSE
            Result = Replace(Result, "error_message", "")
          END IF
        ELSE
          Result = "403"
        END IF
      CASE "reset-password"
        SiteTitle = Language("module_users_reset_password")
        Result = LoadTplFile(ModulePath, "reset-password.html")
        IF QueryString("username") <> "" THEN
          'Resetting password
          UserAlias = ValidateChar(LCASE(QueryString("username")))
          IF UserAlias <> "" THEN
            UserFile = DataPath + "users" + PATH_DELIMITER + UserAlias + ".txt"
            FileContent = ReadFile(UserFile)
            IF FileContent <> "" THEN
              'Check the received token against the stored token
              IF ValidateChar(LCASE(QueryString("token"))) = SplitVar(FileContent, "user_token", LINE_ENDING) THEN
                IF Post("new-user-password") <> "" THEN
                  'Generate new salt
                  SaltLen = 8
                  NewSalt = RandomString(SaltLen)
                  'FIXME: Verify new password not empty
                  FileContent = Replace(FileContent, SplitVar(FileContent, "user_password", LINE_ENDING), NewSalt + "+" + SHA256(NewSalt + Post("new-user-password")))
                  WriteFile(UserFile, FileContent)
                  'Clean memory
                  FileContent = ""
                  Result = LoadTplFile(ModulePath, "reset-password-changed.html")
                ELSE
                  'Show password change form
                  Result = LoadTplFile(ModulePath, "reset-password-change-form.html")
                END IF
              ELSE
                Result = Replace(Result, "error_message", Language("error_wrong_user_token"))
              END IF
            ELSE
              Result = Replace(Result, "error_message", Language("error_user_not_registered"))
            END IF
          ELSE
            Result = Replace(Result, "error_message", Language("error_user_name_empty"))
          END IF
        ELSE
          IF Post("user-captcha") <> "" AND Post("captcha-number") <> "" THEN
            IF ValidateCaptcha(Post("user-captcha"), Post("captcha-number")) THEN 
              UserAlias = ValidateChar(LCASE(Post("user-alias")))
              IF UserAlias <> "" THEN
                UserFile = DataPath + "users" + PATH_DELIMITER + UserAlias + ".txt"
                FileContent = ReadFile(UserFile)
                IF FileContent <> "" THEN
                  'Generate new token
                  TokenLen = 16
                  UserToken = RandomString(TokenLen)
                  'Get user email and alias from file
                  UserEmail = SplitVar(FileContent, "user_email", LINE_ENDING)
                  UserAlias = SplitVar(FileContent, "user_alias", LINE_ENDING)
                  'Update token in user file
                  FileContent = Replace(FileContent, SplitVar(FileContent, "user_token", LINE_ENDING), UserToken)
                  WriteFile(UserFile, FileContent)
                  'Clean memory
                  FileContent = ""
                  'Parse email template
                  TempContent = LoadTplFile(ModulePath, "reset-password-mail.html")
                  TempContent = Replace(TempContent, "user_alias", UserAlias)
                  TempContent = Replace(TempContent, "user_token", UserToken)
                  'Send new activation link
                  'SendMail(UserEmail, UserAlias, Settings("site_email"), Settings("site_title"), SiteTitle, TempContent)
                  'Hide user email
                  DIM AS STRING HiddenEmail, char
                  DIM AS SHORT i
                  FOR i = 1 TO LEN(UserEmail)
                    char = MID(UserEmail, i, 1)
                    SELECT CASE i
                      CASE 4 TO LEN(UserEmail) - 8
                        IF NOT char = "@" THEN
                          char = "*"
                        END IF
                        HiddenEmail = HiddenEmail + char
                      CASE ELSE
                        HiddenEmail = HiddenEmail + char
                    END SELECT
                  NEXT i
                  Result = LoadTplFile(ModulePath, "reset-password-request-sent.html")
                  Result = Replace(Result, "hidden_email", HiddenEmail)
                ELSE
                  Result = Replace(Result, "error_message", Language("error_user_not_registered"))
                END IF
              ELSE
                Result = Replace(Result, "error_message", Language("error_user_name_empty"))
              END IF
           ELSE
              Result = Replace(Result, "error_message", Language("error_captcha_wrong_answer"))
            END IF
          ELSE
            Result = Replace(Result, "error_message", "")
          END IF
        END IF          
      CASE "activate"
        SiteTitle = Language("module_users_login")
        Result = LoadTplFile(ModulePath, "login.html")
        UserAlias = ValidateChar(LCASE(QueryString("username")))
        UserFile = DataPath + "users" + PATH_DELIMITER + UserAlias + ".txt"
        FileContent = ReadFile(UserFile)
        IF FileContent <> "" THEN
          'Check received token against stored token
          IF ValidateChar(LCASE(QueryString("token"))) = SplitVar(FileContent, "user_token", LINE_ENDING) THEN
            'The user must not be active
            IF SplitVar(FileContent, "user_status", LINE_ENDING) = "0" THEN
              FileContent = Replace(FileContent, "user_status=0", "user_status=-1")
              WriteFile(UserFile, FileContent)
              'Clean memory
              FileContent = ""
              Result = Replace(Result, "error_message", Language("success_user_activated"))
            ELSE
              Result = Replace(Result, "error_message", Language("error_user_already_active"))
            END IF
          ELSE
            Result = Replace(Result, "error_message", Language("error_wrong_user_token"))
          END IF
        ELSE
          Result = Replace(Result, "error_message", Language("error_user_not_registered"))
        END IF
    END SELECT
  ELSE
    IF GetCookie(SessionName) <> "" THEN
      SiteTitle = Language("module_users_login")
      Result = LoadTplFile(ModulePath, "login.html")
      IF IsAuth() THEN
        IF UserData <> "" THEN
          IF CINT(SplitVar(UserData, "user_status", LINE_ENDING)) THEN
            SiteTitle = Language("module_users_control_panel")
            Result = LoadTplFile(ModulePath, "user.html")
          ELSE
            Result = Replace(Result, "error_message", Language("error_user_not_active"))
          END IF
        ELSE
          Result = Replace(Result, "error_message", Language("error_user_not_registered"))
        END IF
      ELSE
        Result = Replace(Result, "error_message", Language("error_session_not_exists"))
      END IF
    ELSE
      SiteTitle = Language("module_users_login")
      Result = LoadTplFile(ModulePath, "login.html")
      Result = Replace(Result, "error_message", "")
    END IF
  END IF
  SiteContent = Result
  'Clean memory
  Result = ""
END SUB

FUNCTION ReadUserData(Key AS STRING) AS STRING
  DIM AS STRING Query, Value
  IF Key <> "" THEN
    Query = MID(Key, 1, INSTR(Key, "_") - 1)
    Value = MID(Key, INSTR(Key, "_") + 1)
    SELECT CASE Query
      CASE "session"
        ReadUserData = ReadSession("session_" + Value)
      CASE "user"
        ReadUserData = SplitVar(UserData, "user_" + Value, LINE_ENDING)
      CASE ELSE
        ReadUserData = Key
    END SELECT
  END IF
END FUNCTION