Random Access Files for User Name and Password

by Welopez

One of the tasks many new users would like to do is create a routine to get a user name and compare it to passwords before opening a program. This tutorial is designed to assist you with creating a random access file, inserting user names and passwords, getting input from a user, comparing it to data in a random access file, and then opening the program if the user passes.

This is ONLY A DEMO, using the MAINWIN of JB, but the program flow will be very similar if you are using a GUI. For a true password routine, you must use a GRAPHICBOX or WINDOW FOR GRAPHICS in order to hide the password entered from prying eyes by substituting "*" or "|", or any character which suits you. With Liberty Basic you have access to the PASSWORD command to do the job for you; in Just Basic we must resort to a workaround by trapping the INKEY$ and printing a substitute character on the screen. See the helpfile for using INKEY$, which only works with graphics, as explained above.

      'Random Access Password File
      'by Welopez, 072906

      [reDo]
      CLS
      DIM info$(10, 10)
      'check to see if the pwData.txt file has already been created
      'if true, then skip creation and goto login
      'else create pwData.txt with dummy users and passwords
      IF fileExists(DefaultDir$, "pwData.txt") THEN [login]

      'these are the sample users and passwords for this DEMO
      DATA "GeorgeW", "POTUS"  'and you thought it was Washington?
      DATA "CassieS", "Heyitsme"
      DATA "RalphCramdon", "theGreat1"  'never one of my favorite shows
      DATA "END", "END"

      OPEN "pwData.txt" FOR RANDOM AS #1 LEN=40
      FIELD #1,_
      20 AS user$,_    'maximum 20 characters for user name as string
      20 AS pass$      'maximum 20 characters for password as string

      WHILE user$<>"END"
          k=k+1
          READ user$, pass$
          user$=UPPER$(user$)  'convert to upper case before saving
          pass$=UPPER$(pass$)  'convert to upper case before saving
          PUT #1, k
      WEND
      PRINT "We created the password file!!"
      CLOSE #1

      [login]
      'get username and password
      INPUT "Enter your username: "; id$  'must be different variable name than user$
      INPUT "Enter your password: "; pw$  'must be different variable name than pass$
          id$=UPPER$(id$)  'convert to upper case before comparison
          pw$=UPPER$(pw$)  'convert to upper case before comparison
          PRINT id$, pw$

      OPEN "pwData.txt" FOR RANDOM AS #1 LEN=40
      FIELD #1,_
      20 AS user$,_
      20 AS pass$

      FOR k=1 TO (LOF(#1)/40)
          GET #1, k
          IF (id$=TRIM$(user$)) AND (pw$=TRIM$(pass$)) THEN
          PRINT
          PRINT "Hooray!  You are a recognized user and your password is OK!"
          flag=1
          END IF
      NEXT k
          IF flag<>1 THEN
          PRINT
          PRINT "Sorry, "; id$; ", I don't know you.  Be gone!"
          END IF
      CLOSE #1

      INPUT "Run again? (Y/N) "; response$
      response$=UPPER$(response$)
      IF LEFT$(response$,1)="Y" THEN [reDo]

      '===== This would be the place to insert your code if your user is recognized.
      '===== Exit this DEMO program.
      PRINT
      PRINT "Program terminated."

      END

      FUNCTION fileExists(path$, filename$)
        FILES path$, filename$, info$()
        fileExists = VAL(info$(0, 0))  'non zero is true
      END FUNCTION

The first thing this DEMO does is check to see if pwData.txt currently exists in the default directory. If it does not, it will be created and saved with three users and passwords for the demo.

We'll use a random access file for this DEMO, since many users have expressed a desire to know how to create and use RAF files. The first thing we must do is define the file structure.

      OPEN "pwData.txt" FOR RANDOM AS #1 LEN=40
      FIELD #1,_
      20 AS user$,_    'maximum 20 characters for user name as string
      20 AS pass$      'maximum 20 characters for password as string

The first difference you will note in the OPEN line is LEN=40. This specifies the total length of one complete record in the file. Our file has only 4 records, but a business file might have thousands, even millions of records. If you had a million records, you could get the correct one quickly by the "divide and conquer" method, halving the remaining records until you find the right one.

The next line specifies the fields for the file opened as #1. Every record in this file will have 20 characters AS user$, followed by 20 characters AS pass$. The logical line extension (_) is required for all but the last line. Twenty characters each should be adequate for a user name and password, but you can specify many fields for your records, and an assortment of field lengths. Data can be mixed numeric or alphanumeric, and can include commas and punctuation, such as you might have in an address file if apartment numbers or building numbers are included in the addresses. While the maximum program size for JB may be around 70 MB, this limitation does not apply to random access files stored on disk, unless you try to load the entire file into one or more arrays.

When we were using sequential files, we had to OPEN the file for INPUT, OUTPUT, BINARY, or APPEND. Sequential files can only retrieve data from files opened for INPUT, and can only write to files opened for OUTPUT or APPEND. You can read from or print to files opened for BINARY, but only by specifying the SEEK location where the data exists. Obviously this is not what we wish to do with a password file.

Records are read from a RAF with the GET statement, some manipulation or comparison may be performed, and records are written back to the file with the PUT statement. When we created this file during the first run of this program, we PUT #1, k (the value of our increment counter), which consists of user$ and pass$. If user$=JOE and pass$=BLOW, each string will be automatically padded to 20 characters, and the total of the two fields will equal 40 characters as we specified when we issued the OPEN statement.

This block reads the data in our program, and PUTs the user name and password to record k in pwData.txt. "END" is used as a sentinel to prevent an Out of data error.

      WHILE user$<>"END"
          k=k+1
          READ user$, pass$
          user$=UPPER$(user$)  'convert to upper case before saving
          pass$=UPPER$(pass$)  'convert to upper case before saving
          PUT #1, k
      WEND

To reduce input errors, the user name and password are converted to UPPER$. Later we will get INPUT from the user and convert the INPUT to UPPER$ for comparison.

Now that we have created pwData.txt and understand a little about it, we can ask for INPUT from the user and compare the INPUT to the existing data to ascertain if the user is authorized. We only have 4 records in this file and comparing all of them can be done in the blink of an eye. Even if we had 1,000 user names and passwords, execution would take less than a second.

This block gets the users name and password.

      INPUT "Enter your username: "; id$  'must be different variable name than user$
      INPUT "Enter your password: "; pw$  'must be different variable name than pass$
          id$=UPPER$(id$)  'convert to upper case before comparison
          pw$=UPPER$(pw$)  'convert to upper case before comparison
          PRINT id$, pw$

Then we use a FOR/NEXT loop to compare the user's id$ and pw$ to every record in the file. If we find the user name and correct password, the user is welcomed to the program, ELSE he is told to take a hike. A commercial program might allow the user three attempts before locking him out, and you can write a simple routine to do the same thing. See [http://jbusers.com/phpBB/viewtopic.php?p=100#100] for an example of an encrypted password routine and help file, which allows three failures before locking the user out.

      FOR k=1 TO (LOF(#1)/40)
          GET #1, k
          IF (id$=TRIM$(user$)) AND (pw$=TRIM$(pass$)) THEN
          PRINT
          PRINT "Hooray!  You are a recognized user and your password is OK!"
          flag=1
          END IF
      NEXT k
          IF flag<>1 THEN  'all flags initialize as zero, unless changed during program run
          PRINT
          PRINT "Sorry, "; id$; ", I don't know you.  Be gone!"
          END IF
      CLOSE #1

Our FOR/NEXT loop begins with record 1, and continues TO (LOF(#1)/40). We know each record consists of 40 bytes, so dividing LOF(#1) by 40 yields the number of records in the file and establishes the upper limit of our loop. We must use the TRIM$() function to remove the trailing spaces from user$ and pass$, or the comparison to id$ and pw$ will fail.

Unlike the old fashioned card catalogue in your local library, when you GET a record from your random access file, you have not actually removed it from the file and you do not need to PUT it back unless you have changed the record and wish to save the changes. If you're not old enough to remember library card catalogues, trust me, the head librarian will not rap your knuckles with a ruler unless you fail to close your file before exiting the program. The JB file librarian is very fussy about opening and closing files