Enabled user registration

This commit is contained in:
Deon George
2018-12-25 12:48:57 +11:00
parent cb2d7936d0
commit 128002f434
26 changed files with 854 additions and 149 deletions

48
app/Classes/Control.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
namespace App\Classes;
use App\Classes\Control\Register;
use App\Classes\Control\Telnet;
abstract class Control
{
protected $complete = FALSE;
protected $so = NULL;
public $state = [];
public function __construct(Server $so) {
$this->so = $so;
$this->boot();
}
// Default boot method if a child class doesnt have one.
protected function boot() {
$this->state['mode'] = FALSE;
}
/**
* Has control completed?
*/
public function complete()
{
return $this->complete;
}
// @todo Change to Dynamic Calls by the existence of files in App\Classes\Control
public static function factory(string $name, Server $so) {
switch ($name) {
case 'register':
return new Register($so);
case 'telnet':
return new Telnet($so);
default:
throw new \Exception('Unknown control method: '.$name);
}
}
abstract public function handle(string $char);
}

View File

@@ -0,0 +1,190 @@
<?php
namespace App\Classes\Control;
use App\Classes\Control;
use App\Mail\SendToken;
use App\User;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Validator;
/**
* Class Register handles registration
*
* @todo REMOVE the force .WHITE at the end of each sendBaseline()
*
* @package App\Classes\Control
*/
class Register extends Control
{
private $data = [];
protected function boot()
{
$this->so->sendBaseline($this->so->client(),GREEN.'Select User Name'.WHITE);
}
/**
* Handle Registration Form Input
*
* This function assumes the form has 7 fields in a specific order.
*
* @todo Make this form more dynamic, or put some configuration in a config file, so that there is flexibility
* in field placement.
* @param string $read
* @param array $current
* @return string
*/
public function handle(string $read,array $current=[])
{
// Ignore CR
if ($read == CR)
return '';
// If we got a # we'll be completing field input.
if ($read == HASH OR $read == LF) {
// Our registration page
// @todo get this from the DB
if ($current['page']['frame'] == '981') {
// Does our field have data...
if (array_get($current['fielddata'],$current['fieldnum'])) {
switch ($current['fieldnum']) {
// Username
case 0:
// See if the requested username already exists
if (User::where('login', $current['fielddata'][$current['fieldnum']])->exists()) {
$this->so->sendBaseline($this->so->client(), RED . 'USER ALREADY EXISTS'.WHITE);
return '';
}
$this->data['user'] = $current['fielddata'][$current['fieldnum']];
$this->so->sendBaseline($this->so->client(), GREEN . 'Enter Real Name'.WHITE);
break;
// Real Name
case 1:
$this->data['name'] = $current['fielddata'][$current['fieldnum']];
$this->so->sendBaseline($this->so->client(), GREEN . 'Enter Email Address'.WHITE);
break;
// Email Address
case 2:
if (Validator::make(['email'=>$current['fielddata'][$current['fieldnum']]],[
'email'=>'email',
])->fails()) {
$this->so->sendBaseline($this->so->client(), RED . 'INVALID EMAIL ADDRESS'.WHITE);
return '';
};
// See if the requested email already exists
if (User::where('email', $current['fielddata'][$current['fieldnum']])->exists()) {
$this->so->sendBaseline($this->so->client(), RED . 'USER ALREADY EXISTS'.WHITE);
return '';
}
$this->data['email'] = $current['fielddata'][$current['fieldnum']];
$this->data['token'] = sprintf('%06.0f',rand(0,999999));
$this->so->sendBaseline($this->so->client(), YELLOW . 'PROCESSING...'.WHITE);
Mail::to($this->data['email'])->sendNow(new SendToken($this->data['token']));
if (Mail::failures()) {
dump('Failure?');
dump(Mail::failures());
}
$this->so->sendBaseline($this->so->client(), GREEN . 'Enter Password'.WHITE);
break;
// Enter Password
case 3:
$this->data['password'] = $current['fielddata'][$current['fieldnum']];
$this->so->sendBaseline($this->so->client(), GREEN . 'Confirm Password'.WHITE);
break;
// Confirm Password
case 4:
if ($this->data['password'] !== $current['fielddata'][$current['fieldnum']]) {
$this->so->sendBaseline($this->so->client(), RED . 'PASSWORD DOESNT MATCH, *09 TO START AGAIN'.WHITE);
return '';
}
$this->so->sendBaseline($this->so->client(), GREEN . 'Enter Location'.WHITE);
break;
// Enter Location
case 5:
$this->data['location'] = $current['fielddata'][$current['fieldnum']];
$this->so->sendBaseline($this->so->client(), GREEN . 'Enter TOKEN emailed to you'.WHITE);
break;
// Enter Token
case 6:
if ($this->data['token'] !== $current['fielddata'][$current['fieldnum']]) {
$this->so->sendBaseline($this->so->client(), RED . 'TOKEN DOESNT MATCH, *09 TO START AGAIN'.WHITE);
return '';
}
break;
default:
$this->so->sendBaseline($this->so->client(), RED . 'HUH?');
}
} else {
// If we are MODE_BL, we need to return the HASH, otherwise nothing.
if (in_array($this->state['mode'],[MODE_BL,MODE_SUBMITRF,MODE_RFNOTSENT])) {
return $read;
} else {
$this->so->sendBaseline($this->so->client(), RED . 'FIELD REQUIRED...'.WHITE);
return '';
}
}
}
}
return $read;
}
public function process()
{
$o = new User;
$o->login = $this->data['user'];
$o->email = $this->data['email'];
$o->password = $this->data['password'];
$o->name = $this->data['name'];
$o->location = $this->data['location'];
try {
$o->save();
$this->so->sendBaseline($this->so->client(), GREEN . 'ACCOUNT CREATED, PRESS '.HASH.' TO CONTINUE...'.WHITE);
$this->state['action'] = ACTION_NEXT;
// Add to CUG 0
$o->cugs()->attach(0);
} catch (\Exception $e) {
$this->so->sendBaseline($this->so->client(), RED . 'SOMETHING WENT WRONG...'.WHITE);
$this->so->log('error',$e->getMessage());
$this->state['action'] = ACTION_RELOAD;
}
$this->complete = TRUE;
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace App\Classes\Control;
use App\Classes\Control;
/**
* Class Telnet
*
* This class looks after any telnet session commands
*
* TELNET http://pcmicro.com/netfoss/telnet.html
*
* @package App\Classes\Control
*/
class Telnet extends Control
{
private $option = FALSE;
private $note = '';
private $terminal = '';
public function handle(string $read)
{
$this->state['mode'] = FALSE;
$this->so->log('debug',sprintf('Session Char (%s)',ord($read)),['complete'=>$this->complete,'option'=>$this->option]);
switch ($read) {
// Command being sent.
case TCP_IAC:
$this->complete = FALSE;
$this->note = 'IAC ';
break;
case TCP_SB:
$this->option = TRUE;
break;
case TCP_SE:
$this->option = FALSE;
$this->complete = TRUE;
$this->so->log('debug',sprintf('Session Terminal: %s',$this->terminal));
break;
case TCP_DO:
$this->note .= 'DO ';
break;
case TCP_WILL:
$this->note .= 'WILL ';
break;
case TCP_WONT:
$this->note .= 'WONT ';
break;
case TCP_OPT_TERMTYPE:
break;
case TCP_OPT_ECHO:
$this->note .= 'ECHO';
$this->complete = TRUE;
$this->so->log('debug',sprintf('Session Note: %s',$this->note));
break;
case TCP_OPT_SUP_GOAHEAD:
$this->note .= 'SUPPRESS GO AHEAD';
$this->complete = TRUE;
$this->so->log('debug',sprintf('Session Note: %s',$this->note));
break;
case TCP_OPT_WINDOWSIZE:
$this->note .= 'WINDOWSIZE';
$this->complete = TRUE;
$this->so->log('debug',sprintf('Session Note: %s',$this->note));
break;
default:
if ($this->option AND $read) {
$this->terminal .= $read;
} else {
$this->so->log('debug',sprintf('Unhandled char in session_init: %s (%s)',$read,ord($read)));
}
}
return '';
}
}

View File

@@ -280,7 +280,7 @@ abstract class Frame
*/
public function isFramePublic(): bool
{
return $this->frame->closed ? FALSE : TRUE;
return $this->frame->public ? TRUE : FALSE;
}
// @todo To implement
@@ -401,7 +401,7 @@ abstract class Frame
$o->frame = 999;
$o->index = 'a';
$o->access = 1;
$o->closed = 0;
$o->public = 1;
$o->cls = 1;
// Header

View File

@@ -2,6 +2,6 @@
namespace App\Classes;
class Parser
abstract class Parser
{
}

View File

@@ -16,6 +16,7 @@ abstract class Server {
private $mo = NULL; // Our Mode object
private $co = NULL;
protected $blp = 0; // Size of Bottom Line Pollution
protected $baseline = ''; // Whats on the baseline currently
protected $pid = NULL; // Client PID
public function __construct(Mode $o)
@@ -40,6 +41,9 @@ abstract class Server {
define('ACTION_SUBMITRF', 7); // Offer to submit a response frame
define('ACTION_STAR', 8);
define('CONTROL_TELNET', 1); // Telnet session control
define('CONTROL_METHOD', 2); // Send input to an external method
// Keyboard presses
define('KEY_DELETE', chr(8));
define('KEY_LEFT', chr(136));
@@ -114,9 +118,7 @@ abstract class Server {
// We are now the child.
try {
$session_init = $session_option = FALSE;
$session_note = ''; // TCP Session Notice
$session_term = ''; // TCP Terminal Type
$session = NULL; // TCP Session Details
$client->send(TCP_IAC . TCP_DO . TCP_OPT_SUP_GOAHEAD); // DO SUPPRES GO AHEAD
$client->send(TCP_IAC . TCP_WONT . TCP_OPT_LINEMODE); // WONT LINEMODE
@@ -134,6 +136,8 @@ abstract class Server {
$cmd = ''; // Current *command being typed in
$mode = FALSE; // Current mode.
$user = new User; // The logged in user
$control = FALSE; // Logic in control
$method = collect(); // Method in control for CONTROL_METHOD
$current = []; // Attributes about the current page
// field/fieldnum indexes are for fields on the active page
@@ -161,85 +165,52 @@ abstract class Server {
$read = NULL;
if ($read != '') {
// Client initiation input
// TELNET http://pcmicro.com/netfoss/telnet.html
if ($read == TCP_IAC OR $session_init OR $session_option) {
$this->log('debug',sprintf('Session Char (%s)',ord($read)),['init'=>$session_init,'option'=>$session_option]);
if ($read == TCP_IAC) {
switch ($read) {
// Command being sent.
case TCP_IAC:
$session_init = TRUE;
$session_note = 'IAC ';
// If we are not already in a TELNET LOOP
if ($control !== CONTROL_TELNET) {
$control = CONTROL_TELNET;
continue 2;
// Remember our Telnet Session Object
// @todo We might need to clear out the old mode/action states
if (! $session) {
$session = Control::factory('telnet',$this);
}
case TCP_SB:
$session_option = TRUE;
$method->push($session);
}
}
continue 2;
if ($control AND $method->count()) {
printf("= Control going to method: %s\n", get_class($method->last()));
case TCP_SE:
$session_option = $session_init = FALSE;
$this->log('debug',sprintf('Session Terminal: %s',$session_term));
$read = '';
// Capture our state when we enter this method.
if (! array_key_exists('control',$method->last()->state)) {
$method->last()->state['control'] = $control;
$method->last()->state['action'] = $action;
}
break;
$method->last()->state['mode'] = $mode;
$action = FALSE;
case TCP_DO:
$session_note .= 'DO ';
// Pass Control to Method
$read = $method->last()->handle($read,$current);
$mode = $method->last()->state['mode'];
continue 2;
if ($method->last()->complete()) {
printf("- Control complete: %s\n",get_class($method->last()));
$save = $method->pop();
case TCP_WILL:
$session_note .= 'WILL ';
if ($method->count()) {
$control = $method->last()->state['control'];
continue 2;
} else {
$mode = $save->state['mode'];
$action = $save->state['action'];
$control = FALSE;
}
case TCP_WONT:
$session_note .= 'WONT ';
continue 2;
case TCP_OPT_TERMTYPE:
continue 2;
case TCP_OPT_ECHO:
$session_note .= 'ECHO';
$session_init = FALSE;
$read = '';
$this->log('debug',sprintf('Session Note: %s',$session_note));
continue;
case TCP_OPT_SUP_GOAHEAD:
$session_note .= 'SUPPRESS GO AHEAD';
$session_init = FALSE;
$read = '';
$this->log('debug',sprintf('Session Note: %s',$session_note));
continue;
case TCP_OPT_WINDOWSIZE:
$session_note .= 'WINDOWSIZE';
$session_init = FALSE;
$read = '';
$this->log('debug',sprintf('Session Note: %s',$session_note));
continue;
default:
if ($session_option AND $read) {
$session_term .= $read;
$read = '';
} else {
$this->log('debug',sprintf('Unhandled char in session_init: %s (%s)',$read,ord($read)));
}
dump(sprintf('End: Control is now: %s: Method Count: %s',is_object($control) ? get_class($control) : serialize($control),$method->count()));
}
}
@@ -261,6 +232,8 @@ abstract class Server {
{
$action = ACTION_GOTO;
$page = ['frame'=>'981']; // @todo This should be in the DB.
break 2;
}
}
@@ -303,6 +276,7 @@ abstract class Server {
$action = ACTION_STAR;
$current['fieldpos'] = 0;
$fielddata[$current['fieldnum']] = '';
$current['fielddata'][$current['fieldnum']] = '';
break;
@@ -312,6 +286,7 @@ abstract class Server {
$current['fieldpos']--;
$client->send(LEFT.$fo::$if_filler.LEFT);
$fielddata[$current['fieldnum']] = substr($fielddata[$current['fieldnum']],0,-1);
$current['fielddata'][$current['fieldnum']] = substr($current['fielddata'][$current['fieldnum']],0,-1);
}
break;
@@ -360,15 +335,19 @@ abstract class Server {
break;
case ESC:
break;;
break;
// Record Data Entry
default:
if (ord($read) > 31 && $current['fieldpos'] < $current['field']->length) {
if (! array_get($fielddata,$current['fieldnum']))
if (! array_key_exists($current['fieldnum'],$current['fielddata'])) {
$current['fielddata'][$current['fieldnum']] = '';
$fielddata[$current['fieldnum']] = '';
}
$fielddata[$current['fieldnum']]{$current['fieldpos']} = $read; // @todo delete
$current['fielddata'][$current['fieldnum']]{$current['fieldpos']} = $read;
$fielddata[$current['fieldnum']]{$current['fieldpos']} = $read;
$current['fieldpos']++;
$client->send($fo->isFieldMasked($current['field']->type) ?: $read);
@@ -393,7 +372,11 @@ abstract class Server {
case '1':
$route = $fo->route(1);
if ($route == '*' OR is_numeric($route)) {
// If we are in a control method, complete it
if ($control AND $method->count()) {
$method->last()->process();
} elseif ($route == '*' OR is_numeric($route)) {
$this->sendBaseline($client,RED.'NO ACTION PERFORMED');
$mode = MODE_RFSENT;
@@ -415,9 +398,13 @@ abstract class Server {
break;
case '2':
$this->sendBaseline($client,MSG_NOTSENT);;
$this->sendBaseline($client,MSG_NOTSENT);
$mode = MODE_RFNOTSENT;
// If a Control method was rejected, we can clear it
if ($control AND $method->count())
$method->pop();
break;
case STAR:
@@ -564,10 +551,11 @@ abstract class Server {
}
// Toggle Timewarp Mode
// @todo in forms, the cursor is in the wrong location for ANSI
if ($cmd === '01') {
$client->send(COFF);
$timewarp = !$timewarp;
$this->sendBaseline($client,($timewarp ? MSG_TIMEWARP_ON : MSG_TIMEWARP_OFF));
$this->sendBaseline($client, ($timewarp ? MSG_TIMEWARP_ON : MSG_TIMEWARP_OFF));
$cmd = '';
$action = $mode = FALSE;
@@ -575,6 +563,7 @@ abstract class Server {
}
// Present Timewarp Frames
// @todo in forms, the cursor is in the wrong location for ANSI
if ($cmd === '02') {
$client->send(COFF);
$action = ACTION_INFO;
@@ -583,6 +572,14 @@ abstract class Server {
break;
}
// Report a problem
if ($cmd === '08') {
$this->sendBaseline($client, RED.'NOT IMPLEMENTED YET?');
$read = STAR;
break;
}
// Reload page
if ($cmd === '09') {
$client->send(COFF);
@@ -595,12 +592,14 @@ abstract class Server {
// Another star aborts the command.
if ($read === STAR) {
$action = FALSE;
$this->sendBaseline($client,'');
$this->sendBaseline($client,array_get($current,'baseline',''));
$cmd = '';
if ($current['prevmode'] == MODE_FIELD) {
$mode = $current['prevmode'];
$current['prevmode'] = FALSE;
// @todo The cursor color could be wrong
$client->send($this->outputPosition($current['field']->x,$current['field']->y).CON);
$client->send(str_repeat($fo::$if_filler, $current['field']->length));
$current['fieldreset'] = TRUE;
@@ -654,6 +653,10 @@ abstract class Server {
case ACTION_STAR:
echo "+ Star command...\n";
// If there is something on the baseline, lets preserve it
if ($this->blp)
$current['baseline'] = $this->baseline;
$this->sendBaseline($client,GREEN.STAR,TRUE);
$client->send(CON);
$action = FALSE;
@@ -773,6 +776,8 @@ abstract class Server {
$history->push($page);
}
printf("+ Mode is: %s\n",$mode);
// drop into
case ACTION_RELOAD:
$this->sendBaseline($client,'');
@@ -793,11 +798,25 @@ abstract class Server {
// Login Frame.
case Frame::FRAMETYPE_LOGIN:
$client->send($output);
$output = '';
// If this is the registration page
// @todo Should be evaluated out of the DB
if ($fo->page() == '981a') {
$control = CONTROL_METHOD;
$method->push(Control::factory('register',$this));
$method->last()->state['control'] = $control;
$method->last()->state['action'] = $action;
$method->last()->state['mode'] = MODE_FIELD;
}
// Active Frame. Prestel uses this for a Response Frame.
case Frame::FRAMETYPE_ACTION:
$client->send($output);
// holds data entered by user.
$fielddata = [];
$current['fielddata'] = [];
if (count($fo->fields)) {
// Get our first editable field.

View File

@@ -13,6 +13,8 @@ class Ansi extends AbstractServer {
define('ESC', chr(27));
define('CON', ESC.'[?25h'); // Cursor On
define('COFF', ESC.'[?25l'); // Cursor Off
define('CSAVE', ESC.'[s'); // Save Cursor position
define('CRESTORE',ESC.'[u'); // Restore to saved position
define('HOME', ESC.'[0;0f');
define('LEFT', ESC.'[D'); // Move Cursor
define('RIGHT', ESC.'[C'); // Move Cursor
@@ -54,14 +56,15 @@ class Ansi extends AbstractServer {
// Abstract function
public function sendBaseline($client,$text,$reposition=FALSE) {
$client->send(ESC.'[24;0f'.$text.
$client->send(CSAVE.ESC.'[24;0f'.$text.
($this->blp > $this->strlenv($text)
? str_repeat(' ',$this->blp-$this->strlenv($text)).
($reposition ? ESC.'[24;0f'.str_repeat(RIGHT,$this->strlenv($text)) : '')
: '')
($reposition ? ESC.'[24;0f'.str_repeat(RIGHT,$this->strlenv($text)) : CRESTORE)
: ($reposition ? '' : CRESTORE))
);
$this->blp = $this->strlenv($text);
$this->baseline = $text;
}
// Abstract function

View File

@@ -3,6 +3,7 @@
namespace App\Console\Commands;
use App\Models\Frame;
use App\Models\Mode;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\ModelNotFoundException;
@@ -15,9 +16,9 @@ class FrameImport extends Command
*/
protected $signature = 'frame:import {frame} {index} {file} '.
'{--access=0 : Is frame accessible }'.
'{--closed=1 : Is frame limited to CUG }'.
'{--public=0 : Is frame limited to CUG }'.
'{--cost=0 : Frame Cost }'.
'{--mode=1 : Frame Emulation Mode }'.
'{--mode=videotex : Frame Emulation Mode }'.
'{--replace : Replace existing frame}'.
'{--type=i : Frame Type}'.
'{--trim : Trim off header (first 40 chars)}';
@@ -56,12 +57,14 @@ class FrameImport extends Command
if (! file_exists($this->argument('file')))
throw new \Exception('File not found: '.$this->argument('file'));
$mo = Mode::where('name',$this->option('mode'))->firstOrFail();
$o = new Frame;
if ($this->option('replace')) {
try {
$o = $o->where('frame',$this->argument('frame'))
->where('index',$this->argument('index'))
->where('mode_id',$this->option('mode'))
->where('mode_id',$mo->id)
->firstOrFail();
} catch (ModelNotFoundException $e) {
@@ -72,7 +75,7 @@ class FrameImport extends Command
} else {
$o->frame = $this->argument('frame');
$o->index = $this->argument('index');
$o->mode_id = $this->option('mode');
$o->mode_id = $mo->id;
}
$o->content = ($this->option('trim'))
@@ -80,10 +83,10 @@ class FrameImport extends Command
: file_get_contents($this->argument('file'));
$o->access = $this->option('access');
$o->closed = $this->option('closed');
$o->public = $this->option('public');
$o->cost = $this->option('cost');
$o->type = $this->option('type');
$o->save();
}
}
}

40
app/Mail/SendToken.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Service;
class SendToken extends Mailable
{
use Queueable, SerializesModels;
public $token = '';
/**
* Create a new message instance.
*
* @return void
*/
public function __construct(string $token)
{
$this->token = $token;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this
->markdown('email.sendtoken')
->subject('Token to complete registration')
->with(['token'=>$this->token]);
}
}

View File

@@ -25,6 +25,18 @@ class Frame extends Model
});
}
/**
* For cockroachDB, content is a "resource stream"
*
* @return bool|string
*/
public function getContentAttribute()
{
return is_resource($this->attributes['content'])
? stream_get_contents($this->attributes['content'])
: $this->attributes['content'];
}
/**
* Return the Page Number
*