Public/Sessions.ps1
<#
.SYNOPSIS Enables Middleware for creating, retrieving and using Sessions within Pode. .DESCRIPTION Enables Middleware for creating, retrieving and using Sessions within Pode; with support for defining Session duration, and custom Storage. If you're storing sessions outside of Pode, you must supply a Secret value so sessions aren't corrupted. .PARAMETER Secret An optional Secret to use when signing Sessions (Default: random GUID). .PARAMETER Name The name of the cookie/header used for the Session. .PARAMETER Duration The duration a Session should last for, before being expired. .PARAMETER Generator A custom ScriptBlock to generate a random unique SessionId. The value returned must be a String. .PARAMETER Storage A custom PSObject that defines methods for Delete, Get, and Set. This allow you to store Sessions in custom Storage such as Redis. A Secret is required. .PARAMETER Scope The Scope that the Session applies to, possible values are Browser and Tab (Default: Browser). The Browser scope is the default logic, where authentication and general data for the sessions are shared across all tabs. The Tab scope keep the authentication data shared across all tabs, but general data is separated across different tabs. For the Tab scope, the "Tab ID" required will be sourced from the "X-PODE-SESSION-TAB-ID" header. .PARAMETER Extend If supplied, the Sessions will have their durations extended on each successful Request. .PARAMETER HttpOnly If supplied, the Session cookie will only be accessible to browsers. .PARAMETER Secure If supplied, the Session cookie will only be accessible over HTTPS Requests. .PARAMETER Strict If supplied, the Secret will be extended using the client request's UserAgent and RemoteIPAddress. .PARAMETER UseHeaders If supplied, Sessions will be sent back in a header on the Response with the Name supplied. .EXAMPLE Enable-PodeSessionMiddleware -Duration 120 .EXAMPLE Enable-PodeSessionMiddleware -Duration 120 -Extend -Generator { return [System.IO.Path]::GetRandomFileName() } .EXAMPLE Enable-PodeSessionMiddleware -Secret 'schwifty' -Duration 120 -UseHeaders -Strict #> function Enable-PodeSessionMiddleware { [CmdletBinding(DefaultParameterSetName = 'Cookies')] param( [Parameter()] [string] $Secret, [Parameter()] [ValidateNotNullOrEmpty()] [string] $Name = 'pode.sid', [Parameter()] [ValidateScript({ if ($_ -lt 0) { # Duration must be 0 or greater, but got throw ($PodeLocale.durationMustBeZeroOrGreaterExceptionMessage -f $_) } return $true })] [int] $Duration = 0, [Parameter()] [scriptblock] $Generator, [Parameter()] [psobject] $Storage = $null, [Parameter()] [ValidateSet('Browser', 'Tab')] [string] $Scope = 'Browser', [switch] $Extend, [Parameter(ParameterSetName = 'Cookies')] [switch] $HttpOnly, [Parameter(ParameterSetName = 'Cookies')] [switch] $Secure, [switch] $Strict, [Parameter(ParameterSetName = 'Headers')] [switch] $UseHeaders ) # check that session logic hasn't already been initialised if (Test-PodeSessionsEnabled) { # Session Middleware has already been initialized throw ($PodeLocale.sessionMiddlewareAlreadyInitializedExceptionMessage) } # ensure the override store has the required methods if (!(Test-PodeIsEmpty $Storage)) { $members = @($Storage | Get-Member | Select-Object -ExpandProperty Name) @('delete', 'get', 'set') | ForEach-Object { if ($members -inotcontains $_) { # The custom session storage does not implement the required '{0}()' method throw ($PodeLocale.customSessionStorageMethodNotImplementedExceptionMessage -f $_) } } } # verify the secret, set to guid if not supplied, or error if none and we have a storage if ([string]::IsNullOrEmpty($Secret)) { if (!(Test-PodeIsEmpty $Storage)) { # A Secret is required when using custom session storage throw ($PodeLocale.secretRequiredForCustomSessionStorageExceptionMessage) } $Secret = Get-PodeServerDefaultSecret } # if no custom storage, use the inmem one if (Test-PodeIsEmpty $Storage) { $Storage = (Get-PodeSessionInMemStore) Set-PodeSessionInMemClearDown } # set options against server context $PodeContext.Server.Sessions = @{ Name = $Name Secret = $Secret GenerateId = (Protect-PodeValue -Value $Generator -Default { return (New-PodeGuid) }) Store = $Storage Info = @{ Duration = $Duration Extend = $Extend.IsPresent Secure = $Secure.IsPresent Strict = $Strict.IsPresent HttpOnly = $HttpOnly.IsPresent UseHeaders = $UseHeaders.IsPresent Scope = @{ Type = $Scope.ToLowerInvariant() IsBrowser = ($Scope -ieq 'Browser') } } } # return scriptblock for the session middleware Get-PodeSessionMiddleware | New-PodeMiddleware | Add-PodeMiddleware -Name '__pode_mw_sessions__' } <# .SYNOPSIS Remove the current Session, logging it out. .DESCRIPTION Remove the current Session, logging it out. This will remove the session from Storage, and Cookies. .EXAMPLE Remove-PodeSession #> function Remove-PodeSession { [CmdletBinding()] param() # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # The sessions have not been configured throw ($PodeLocale.sessionsNotConfiguredExceptionMessage) } # do nothing if session is null if ($null -eq $WebEvent.Session) { return } # remove the session, and from auth and cookies Remove-PodeAuthSession } <# .SYNOPSIS Saves the current Session's data. .DESCRIPTION Saves the current Session's data. .PARAMETER Force If supplied, the data will be saved even if nothing has changed. .EXAMPLE Save-PodeSession -Force #> function Save-PodeSession { [CmdletBinding()] param( [switch] $Force ) # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # The sessions have not been configured throw ($PodeLocale.sessionsNotConfiguredExceptionMessage) } # error if session is null if ($null -eq $WebEvent.Session) { # There is no session available to save throw ($PodeLocale.noSessionAvailableToSaveExceptionMessage) } # if auth is in use, then assign to session store if (!(Test-PodeIsEmpty $WebEvent.Auth) -and $WebEvent.Auth.Store) { $WebEvent.Session.Data.Auth = $WebEvent.Auth } # save the session Save-PodeSessionInternal -Force:$Force } <# .SYNOPSIS Returns the currently authenticated SessionId. .DESCRIPTION Returns the currently authenticated SessionId. If there's no session, or it's not authenticated, then null is returned instead. You can also have the SessionId returned as signed as well. .PARAMETER Signed If supplied, the returned SessionId will also be signed. .PARAMETER Force If supplied, the sessionId will be returned regardless of authentication. .EXAMPLE $sessionId = Get-PodeSessionId #> function Get-PodeSessionId { [CmdletBinding()] param( [switch] $Signed, [switch] $Force ) $sessionId = $null # do nothing if not authenticated, or force passed if (!$Force -and ((Test-PodeIsEmpty $WebEvent.Session.Data.Auth.User) -or !$WebEvent.Session.Data.Auth.IsAuthenticated)) { return $sessionId } # get the sessionId $sessionId = $WebEvent.Session.FullId # do they want the session signed? if ($Signed) { $strict = $PodeContext.Server.Sessions.Info.Strict $secret = $PodeContext.Server.Sessions.Secret # sign the value if we have a secret $sessionId = (Invoke-PodeValueSign -Value $sessionId -Secret $secret -Strict:$strict) } # return the ID return $sessionId } function Get-PodeSessionTabId { [CmdletBinding()] param() if ($PodeContext.Server.Sessions.Info.Scope.IsBrowser) { return $null } return Get-PodeHeader -Name 'X-PODE-SESSION-TAB-ID' } <# .SYNOPSIS Resets the current Session's expiry date. .DESCRIPTION Resets the current Session's expiry date, to be from the current time plus the defined Session duration. .EXAMPLE Reset-PodeSessionExpiry #> function Reset-PodeSessionExpiry { [CmdletBinding()] param() # if sessions haven't been setup, error if (!(Test-PodeSessionsEnabled)) { # The sessions have not been configured throw ($PodeLocale.sessionsNotConfiguredExceptionMessage) } # error if session is null if ($null -eq $WebEvent.Session) { # There is no session available to save throw ($PodeLocale.noSessionAvailableToSaveExceptionMessage) } # temporarily set this session to auto-extend $WebEvent.Session.Extend = $true # reset on response Set-PodeSession } <# .SYNOPSIS Returns the defined Session duration. .DESCRIPTION Returns the defined Session duration that all Session are created using. .EXAMPLE $duration = Get-PodeSessionDuration #> function Get-PodeSessionDuration { [CmdletBinding()] [OutputType([int])] param() return [int]$PodeContext.Server.Sessions.Info.Duration } <# .SYNOPSIS Returns the datetime on which the current Session's will expire. .DESCRIPTION Returns the datetime on which the current Session's will expire. .EXAMPLE $expiry = Get-PodeSessionExpiry #> function Get-PodeSessionExpiry { [CmdletBinding()] [OutputType([datetime])] param() # error if session is null if ($null -eq $WebEvent.Session) { # There is no session available to save throw ($PodeLocale.noSessionAvailableToSaveExceptionMessage) } # default min date if ($null -eq $WebEvent.Session.TimeStamp) { return [datetime]::MinValue } # use datetime.now or existing timestamp? $expiry = [DateTime]::UtcNow if (!$WebEvent.Session.Extend -and ($null -ne $WebEvent.Session.TimeStamp)) { $expiry = $WebEvent.Session.TimeStamp } # add session duration on $expiry = $expiry.AddSeconds($PodeContext.Server.Sessions.Info.Duration) # return expiry return $expiry } function Test-PodeSessionsEnabled { return (($null -ne $PodeContext.Server.Sessions) -and ($PodeContext.Server.Sessions.Count -gt 0)) } function Get-PodeSessionInfo { return $PodeContext.Server.Sessions.Info } function Test-PodeSessionScopeIsBrowser { return [bool]$PodeContext.Server.Sessions.Info.Scope.IsBrowser } |