258 lines
7.7 KiB
Go
258 lines
7.7 KiB
Go
// This file is part of https://github.com/racingmars/go3270/
|
|
// Copyright 2025 by Matthew R. Wilson, licensed under the MIT license. See
|
|
// LICENSE in the project root for license information.
|
|
|
|
// This example demonstrates using the RunTransactions() approach to
|
|
// structuring a go3270 application.
|
|
|
|
// This file contains the login and user registration transactions.
|
|
|
|
package main
|
|
|
|
import (
|
|
"net"
|
|
|
|
"github.com/racingmars/go3270"
|
|
)
|
|
|
|
const loginUsername = "username"
|
|
const loginPassword = "password"
|
|
const loginPasswordConf = "password confirmation"
|
|
const loginName = "name"
|
|
const loginErr = "errormsg"
|
|
|
|
var loginScreen = go3270.Screen{
|
|
{Row: 0, Col: 37, Intense: true, Content: "Logon"},
|
|
{Row: 2, Col: 0,
|
|
Content: "Welcome to the go3270 example application. Please log on."},
|
|
|
|
// Username
|
|
{Row: 4, Col: 0, Content: "Username . . .", Color: go3270.Green},
|
|
{Row: 4, Col: 15, Name: loginUsername, Write: true,
|
|
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
|
{Row: 4, Col: 24, Autoskip: true}, // field "stop" character
|
|
|
|
// Password
|
|
{Row: 5, Col: 0, Content: "Password . . .", Color: go3270.Green},
|
|
{Row: 5, Col: 15, Name: loginPassword, Write: true, Hidden: true},
|
|
{Row: 5, Col: 79}, // field "stop" character
|
|
|
|
// Registration instructions
|
|
{Row: 7, Col: 0, Content: "If you don't yet have an account, press"},
|
|
{Row: 7, Col: 40, Content: "PF5", Color: go3270.White, Intense: true},
|
|
{Row: 7, Col: 44, Content: "to register a new account."},
|
|
|
|
// Error message
|
|
{Row: 21, Col: 0, Name: loginErr, Color: go3270.Red, Intense: true},
|
|
|
|
// Key legend
|
|
{Row: 23, Col: 1, Content: "F1=Help"},
|
|
{Row: 23, Col: 14, Content: "F3=Exit"},
|
|
{Row: 23, Col: 27, Content: "F5=Register"},
|
|
}
|
|
|
|
var loginScreenRules = go3270.Rules{
|
|
loginUsername: {Validator: go3270.NonBlank},
|
|
loginPassword: {Validator: go3270.NonBlank, Reset: true},
|
|
}
|
|
|
|
// login transaction accepts a string value in data if the login screen
|
|
// should be initialized with an error message.
|
|
func (sess *session) login(conn net.Conn, data any) (go3270.Tx, any, error) {
|
|
|
|
fieldValues := make(map[string]string)
|
|
|
|
if data != nil {
|
|
if errmsg, ok := data.(string); ok {
|
|
fieldValues[loginErr] = errmsg
|
|
}
|
|
}
|
|
|
|
resp, err := go3270.HandleScreen(
|
|
loginScreen, // the screen to display
|
|
loginScreenRules, // the rules to enforce
|
|
fieldValues, // pre-populated values in fields
|
|
[]go3270.AID{go3270.AIDEnter}, // keys we accept -- validating
|
|
[]go3270.AID{ // keys we accept -- non-validating
|
|
go3270.AIDPF1,
|
|
go3270.AIDPF3,
|
|
go3270.AIDPF5,
|
|
go3270.AIDClear},
|
|
loginErr, // name of field to put error messages in
|
|
4, 16, // cursor coordinates
|
|
conn)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
switch resp.AID {
|
|
case go3270.AIDClear:
|
|
// re-run the transaction with empty values
|
|
return sess.login, nil, nil
|
|
case go3270.AIDPF1:
|
|
// Display help transaction, then return to this transaction
|
|
return help(sess.login), nil, nil
|
|
case go3270.AIDPF3:
|
|
// User wants to quit; return no next transaction
|
|
return nil, nil, nil
|
|
case go3270.AIDPF5:
|
|
// Send user to registration transaction
|
|
return sess.newuser, nil, nil
|
|
}
|
|
|
|
// If we didn't get one of the other allowed keys, try to log the user in.
|
|
username := resp.Values[loginUsername]
|
|
password := resp.Values[loginPassword]
|
|
|
|
user, err := sess.db.GetUser(username)
|
|
// NOTE: this is just an example application. Obviously in a real
|
|
// application, password hashing would be in place.
|
|
if err != nil || user.Password != password {
|
|
// If we couldn't get the username from the DB, or if the password
|
|
// doesn't match, re-run the login transaction with an error
|
|
// message displayed.
|
|
|
|
return sess.login, "Username or password not valid.", nil
|
|
}
|
|
|
|
// Login was successful. Update the session state to the logged-in user.
|
|
sess.user = user
|
|
|
|
return sess.mainmenu, nil, nil
|
|
}
|
|
|
|
var newuserScreen = go3270.Screen{
|
|
{Row: 0, Col: 30, Intense: true, Content: "New User Registration"},
|
|
{Row: 2, Col: 0,
|
|
Content: "Please provide your user registration details."},
|
|
|
|
// Username
|
|
{Row: 4, Col: 0, Content: "Username . . .", Color: go3270.Green},
|
|
{Row: 4, Col: 15, Name: loginUsername, Write: true,
|
|
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
|
{Row: 4, Col: 24, Autoskip: true}, // field "stop" character
|
|
|
|
// Password
|
|
{Row: 5, Col: 0, Content: "Password . . .", Color: go3270.Green},
|
|
{Row: 5, Col: 15, Name: loginPassword, Write: true, Hidden: true},
|
|
{Row: 5, Col: 79, Autoskip: true}, // field "stop" character
|
|
|
|
// Password
|
|
{Row: 6, Col: 0, Content: "Confirm Pass .", Color: go3270.Green},
|
|
{Row: 6, Col: 15, Name: loginPasswordConf, Write: true, Hidden: true},
|
|
{Row: 6, Col: 79, Autoskip: true}, // field "stop" character
|
|
|
|
// Name
|
|
{Row: 7, Col: 0, Content: "Name . . . . .", Color: go3270.Green},
|
|
{Row: 7, Col: 15, Name: loginName, Write: true,
|
|
Highlighting: go3270.Underscore, Color: go3270.Turquoise},
|
|
{Row: 7, Col: 46, Autoskip: true}, // field "stop" character
|
|
|
|
// Error message
|
|
{Row: 21, Col: 0, Name: loginErr, Color: go3270.Red, Intense: true},
|
|
|
|
// Key legend
|
|
{Row: 23, Col: 1, Content: "F1=Help"},
|
|
{Row: 23, Col: 14, Content: "F3=Exit"},
|
|
}
|
|
|
|
var newuserScreenRules = go3270.Rules{
|
|
loginUsername: {Validator: go3270.NonBlank},
|
|
loginPassword: {Validator: go3270.NonBlank, Reset: true},
|
|
loginPasswordConf: {Validator: go3270.NonBlank, Reset: true},
|
|
}
|
|
|
|
// newuserData is the data that can be passed into the newuser transaction
|
|
type newuserData struct {
|
|
username string
|
|
name string
|
|
errmsg string
|
|
}
|
|
|
|
func (sess *session) newuser(conn net.Conn, data any) (go3270.Tx, any, error) {
|
|
|
|
fieldValues := make(map[string]string)
|
|
|
|
if data != nil {
|
|
if newdata, ok := data.(newuserData); ok {
|
|
fieldValues[loginUsername] = newdata.username
|
|
fieldValues[loginName] = newdata.name
|
|
fieldValues[loginErr] = newdata.errmsg
|
|
}
|
|
}
|
|
|
|
resp, err := go3270.HandleScreen(
|
|
newuserScreen, // the screen to display
|
|
newuserScreenRules, // the rules to enforce
|
|
fieldValues, // pre-populated values in fields
|
|
[]go3270.AID{go3270.AIDEnter}, // keys we accept -- validating
|
|
[]go3270.AID{ // keys we accept -- non-validating
|
|
go3270.AIDPF1,
|
|
go3270.AIDPF3,
|
|
go3270.AIDClear,
|
|
},
|
|
loginErr, // name of field to put error messages in
|
|
4, 16, // cursor coordinates
|
|
conn)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
switch resp.AID {
|
|
case go3270.AIDClear:
|
|
// Re-run transaction with empty values
|
|
return sess.newuser, nil, nil
|
|
case go3270.AIDPF1:
|
|
// Display help screen, returning to this transaction after
|
|
return help(sess.newuser), nil, nil
|
|
case go3270.AIDPF3:
|
|
// User wants to cancel registration, go back to login screen.
|
|
return sess.login, nil, nil
|
|
}
|
|
|
|
// Otherwise, we'll try creating the user.
|
|
|
|
username := resp.Values[loginUsername]
|
|
password := resp.Values[loginPassword]
|
|
passwordConf := resp.Values[loginPasswordConf]
|
|
name := resp.Values[loginName]
|
|
|
|
if password != passwordConf {
|
|
// Re-run the newuser transaction with an error message
|
|
return sess.newuser, newuserData{
|
|
username: username,
|
|
name: name,
|
|
errmsg: "Passwords do not match.",
|
|
}, nil
|
|
}
|
|
|
|
user, err := sess.db.CreateUser(User{
|
|
Username: username,
|
|
Password: password,
|
|
Name: name,
|
|
})
|
|
|
|
if err == ErrUserExists {
|
|
// Re-run the newuser transaction with an error message
|
|
return sess.newuser, newuserData{
|
|
username: username,
|
|
name: name,
|
|
errmsg: "Username already exists; please choose a new one.",
|
|
}, nil
|
|
}
|
|
|
|
if err != nil {
|
|
return sess.newuser, newuserData{
|
|
username: username,
|
|
name: name,
|
|
errmsg: "Unknown error creating new user.",
|
|
}, nil
|
|
}
|
|
|
|
// Success! We'll stick the new user in the session state like login does
|
|
// and go to the main menu.
|
|
sess.user = user
|
|
|
|
return sess.mainmenu, nil, nil
|
|
}
|