Skip to content

Commit

Permalink
Merge pull request #90 from Badgerati/develop
Browse files Browse the repository at this point in the history
v0.19.0
  • Loading branch information
Badgerati committed Sep 14, 2018
2 parents 6c5bcf2 + ff0ca2e commit 7a83b9a
Show file tree
Hide file tree
Showing 42 changed files with 1,971 additions and 142 deletions.
224 changes: 216 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ Pode is a Cross-Platform PowerShell framework that allows you to host [REST APIs
* [Middleware](#middleware)
* [Order of Running](#order-of-running)
* [Overriding Inbuilt Logic](#overriding-inbuilt-logic)
* [Sessions](#sessions)
* [Authentication](#authentication)
* [Basic](#basic-auth)
* [Form](#form-auth)
* [Custom](#custom-auth)
* [SMTP Server](#smtp-server)
* [Misc](#misc)
* [Logging](#logging)
Expand Down Expand Up @@ -70,6 +75,8 @@ Pode is a Cross-Platform PowerShell framework that allows you to host [REST APIs
* Basic rate limiting for IP addresses and subnets
* Support for generating/binding self-signed certificates, and binding signed certificates
* Support for middleware on web servers
* Session middleware support on web requests
* Can use authentication on requests, which can either be sessionless or session persistant

## Install

Expand Down Expand Up @@ -480,6 +487,8 @@ Middleware in Pode is executed in a specific order, this order of running is as
* Route middleware - runs any `route` middleware for the current route being processed
* Route - finally, the route itself is processed

> This order will be fully customisable in future releases, which will also remove the overriding logic below
#### Overriding Inbuilt Logic

Pode has some inbuilt middleware, as defined in the order of running above. Sometimes you probably don't want to use the inbuilt rate limiting, and use a custom rate limiting library that utilises REDIS. Each of the inbuilt middlewares have a defined name, that you can pass to the `middleware` function:
Expand Down Expand Up @@ -512,6 +521,209 @@ Server {
}
```

#### Sessions

Session `middleware` is supported in Pode on web requests/responses, in the form of signed-cookies and server-side data storage. When configured, the middleware will check for a session-cookie on the request; if a cookie is not found on the request, or the session is not in the store, then a new session is created and attached to the response. If there is a session, then the appropriate data is loaded from the store.

The age of the session-cookie can be specified (and whether to extend the duration each time), as well as a secret-key to sign cookies, and the ability to specify custom data stores - the default is in-mem, custom could be anything like redis/mongo.

The following is an example of how to setup session middleware:

```powershell
Server {
middleware (session @{
'Secret' = 'schwifty'; # secret-key used to sign session cookie
'Name' = 'pode.sid'; # session cookie name (def: pode.sid)
'Duration' = 120; # duration of the cookie, in seconds
'Extend' = $true; # extend the duration of the cookie on each call
'GenerateId' = { # custom SessionId generator (def: guid)
return [System.IO.Path]::GetRandomFileName()
};
'Store' = $null; # custom object with required methods (def: in-mem)
})
}
```

##### GenerateId

If supplied, the `GenerateId` must be a scriptblock that returns a valid string. The string itself should be a random unique value, that can be used as a session identifier. The default `sessionId` is a `guid`.

##### Store

If supplied, the `Store` must be a valid object with the following required functions:

```powershell
[hashtable] Get([string] $sessionId)
[void] Set([string] $sessionId, [hashtable] $data, [datetime] $expiry)
[void] Delete([string] $sessionId)
```

If no store is supplied, then a default in-memory store is used - with auto-cleanup for expired sessions.

To add data to a session you can utilise the `.Session.Data` object within a `route`. The data will be saved at the end of the route logic autmoatically using `endware`. When a request comes in using the same session, the data is loaded from the store. An example of using a `session` in a `route` to increment a views counter could be as follows (the counter will continue to increment on each call to the route until the session expires):

```powershell
Server {
route 'get' '/' {
param($s)
$s.Session.Data.Views++
json @{ 'Views' = $s.Session.Data.Views }
}
}
```

### Authentication

Using middleware and sessions, Pode has support for authentication on web requests. This authentication can either be session-persistant (ie, logins on websites), or sessionless (ie, auths on rest api calls). Examples of both types can be seen in the `web-auth-basic.ps1` and `web-auth-forms.ps1` example scripts.

To use authentication in Pode there are two key commands: `auth use` and `auth check`.

* `auth use` is used to setup an auth type (basic/form/custom); this is where you specify a validator script (to check the user exists in your storage), any options, and if using a custom type a parser script (to parse headers/payloads to pass to the validator). An example:

```powershell
Server {
# auth use <type> -v {} [-o @{}]
auth use basic -v {
param($user, $pass)
# logic to check user
return @{ 'user' = $user }
}
}
```

The validator (`-v`) script is used to find a user, checking if they exist and the password is correct. If the validator passes, then a `user` needs to be returned from the script via `@{ 'user' = $user }` - if `$null` or a null user are returned then the validator is assumed to have failed, and a 401 status will be thrown.

Some auth methods also have options (`-o`) that can be supplied as a hashtable, such as field name or encoding overrides - more below.

* `auth check` is used in `route` calls, to check a specific auth method against the incoming request. If the validator defined in `auth use` returns no user, then the check fails with a 401 status; if a user is found, then it is set against the session (if session middleware is enabled) and the route logic is invoked. An example:

```powershell
Server {
# auth check <type> [-o @{}]
route get '/users' (auth check basic) {
param($session)
# route logic
}
}
```

This is the most simple call to check authentication, the call also accepts options (`-o`) in a hashtable:

| Name | Description |
| --- | ----------- |
| FailureUrl | URL to redirect to should auth fail |
| SuccessUrl | URL to redirect to should auth succeed |
| Session | When true: check if the session already has a validated user, and store the validated user in the session (def: true) |
| Login | When true: check the auth status in session and redirect to SuccessUrl, else proceed to the page with no auth required (def: false) |
| Logout | When true: purge the session and redirect to the FailureUrl (def: false) |

If you have defined session-middleware to be used in your script, then when an `auth check` call succeeds the user with be authenticated against that session. When the user makes another call using the same session-cookie, then the `auth check` will detect the already authenticated session and skip the validator script. If you're using sessions and you don't want the `auth check` to check the session, or store the user against the session, then pass `-o @{ 'Session' = $false }` to the `auth check`.

> Not defining session middleware is basically like always having `Session = $false` set on `auth check`
#### Basic Auth

> Example with comments in `examples/web-auth-basic.ps1`
Basic authentication is when you pass a encoded username:password value on the header of your requests: `@{ 'Authorization' = 'Basic <base64 encoded username:password>' }`. To setup basic auth in Pode, you specify `auth use basic` in your server script; the validator script will have the username/password supplied as parameters:

```powershell
Server {
auth use basic -v {
param($username, $password)
}
}
```

##### Options

| Name | Description |
| ---- | ----------- |
| Encoding | Defines which encoding to use when decoding the auth header (def: `ISO-8859-1`) |
| Name | Defines the name part of the header, infront of the encoded sting (def: Basic) |

#### Form Auth

> Example with comments in `examples/web-auth-form.ps1`
Form authentication is for when you're using a `<form>` in HTML, and you submit the form. The type expects a `username` and a `password` to be passed from the form input fields. To setup form auth in Pode, you specify `auth use form` in your server script; the validator script will have the username/password supplied as parameters:

```powershell
Server {
auth use form -v {
param($username, $password)
}
}
```

```html
<form action="/login" method="post">
<div>
<label>Username:</label>
<input type="text" name="username"/>
</div>
<div>
<label>Password:</label>
<input type="password" name="password"/>
</div>
<div>
<input type="submit" value="Login"/>
</div>
</form>
```

##### Options

| Name | Description |
| ---- | ----------- |
| UsernameField | Defines the name of field which the username will be passed in from the form (def: username) |
| PasswordField | Defines the name of field which the password will be passed in from the form (def: password) |

#### Custom Auth

Custom authentication works much like the above inbuilt types, but allows you to specify your own parsing logic. For example, let's say we wanted something similar to `form` authentication but it requires a third piece of information: ClientName. To setup a custom authentication, you can use any name and specify the `-c` flag; you'll also be required to specify the parsing scriptblock under `-p`:

```powershell
Server {
auth use -c client -p {
# the current web-session (same data as supplied to routes), and options supplied
param($session, $opts)
# get client/user/pass field names to get from payload
$clientField = (coalesce $opts.ClientField 'client')
$userField = (coalesce $opts.UsernameField 'username')
$passField = (coalesce $opts.PasswordField 'password')
# get the client/user/pass from the post data
$client = $session.Data.$clientField
$username = $session.Data.$userField
$password = $session.Data.$passField
# return the data, to be passed to the validator script
return @($client, $username, $password)
} `
-v {
param($client, $username, $password)
# find the user
# if not found, return null - for a 401
# return the user
return @{ 'user' = $user }
}
route get '/users' (auth check client) {
param($session)
}
}
```

### SMTP Server

Pode can also run as an SMTP server - useful for mocking tests. There are two options, you can either use Pode's inbuilt simple SMTP logic, or write your own using Pode as a TCP server instead.
Expand Down Expand Up @@ -1051,13 +1263,6 @@ Pode comes with a few helper functions - mostly for writing responses and readin
* `csv`
* `view`
* `tcp`
* `Get-PodeRoute`
* `Get-PodeTcpHandler`
* `Get-PodeTimer`
* `Write-ToResponse`
* `Write-ToResponseFromFile`
* `Test-IsUnix`
* `Test-IsPSCore`
* `status`
* `redirect`
* `include`
Expand All @@ -1070,4 +1275,7 @@ Pode comes with a few helper functions - mostly for writing responses and readin
* `dispose`
* `stream`
* `schedule`
* `middleware`
* `middleware`
* `endware`
* `session`
* `auth`
7 changes: 1 addition & 6 deletions examples/external-funcs.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/file-monitoring.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/logging.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/loop-server.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/mail-server.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/middleware.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/nunit-rest-api.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/rest-api.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/schedules.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/shared-state.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down
7 changes: 1 addition & 6 deletions examples/tcp-server.ps1
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
if ((Get-Module -Name Pode | Measure-Object).Count -ne 0)
{
Remove-Module -Name Pode
}

$path = Split-Path -Parent -Path (Split-Path -Parent -Path $MyInvocation.MyCommand.Path)
Import-Module "$($path)/src/Pode.psm1" -ErrorAction Stop
Import-Module "$($path)/src/Pode.psm1" -Force -ErrorAction Stop

# or just:
# Import-Module Pode
Expand Down

0 comments on commit 7a83b9a

Please sign in to comment.