Skip to content

Commit

Permalink
Merge pull request #13 from Sam-Martin/feature/dynamodb
Browse files Browse the repository at this point in the history
DynamoDB
  • Loading branch information
Sam-Martin authored Dec 10, 2018
2 parents bf985e6 + e178601 commit bef6291
Show file tree
Hide file tree
Showing 18 changed files with 628 additions and 402 deletions.
Binary file modified .gitignore
Binary file not shown.
5 changes: 2 additions & 3 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ environment:
nodejs_version: "6"
AWS_ACCESS_KEY_ID:
secure: LkRtWnDBlOLG2njX4yN862dy5pxCW/KmGemd3dtI0pY=
AWS_SECRET_ACCESS_KEY:
AWS_SECRET_ACCESS_KEY:
secure: Nv2ZorbpQ5GUnxB1thyvzWv7rv9XtPOLY98du5A9Zd/+CC9SF9yhcGVhBsxGjdtE
before_build:
- ps: Install-Product node $env:nodejs_version
- ps: Install-Module -Name powershell-yaml
- ps: Install-Module -Name psake
- ps: Install-Module -Name pester
- cmd: npm install serverless -g
- cmd: npm install serverless-s3-sync --save
build_script:
- ps: Set-AWSCredentials -AccessKey $env:AWS_ACCESS_KEY_ID -SecretKey $env:AWS_SECRET_ACCESS_KEY;Invoke-PSake;$host.SetShouldExit([int]!$psake.build_success);
on_finish:
- ps: Set-AWSCredentials -AccessKey $env:AWS_ACCESS_KEY_ID -SecretKey $env:AWS_SECRET_ACCESS_KEY;Invoke-PSake -taskList destroy;$host.SetShouldExit([int]!$psake.build_success);
5 changes: 5 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
stage: dev
public_bucket_name: ephemera-public-sammartin-${self:custom.config.stage}
dynamodb_table_name: ephemera-${self:custom.config.stage}
region: eu-west-2
max_secret_age_hours: "24"
57 changes: 3 additions & 54 deletions default.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ properties {
$global:stage = $stage;
}
}
task default -depends prerequisites,build, deploy-serverless, configure-frontend, deploy-s3bucketcontents, test
task default -depends deploy-serverless, test

task prerequisites {
if(!(Get-Command 'npm')){
Expand All @@ -26,72 +26,21 @@ task prerequisites {
Write-Verbose "Pre-requisites checked successfully"
}

task build {
Push-Location lambda
npm install
Pop-Location
}

task deploy-serverless {

Push-Location serverless-ephemera
serverless deploy --stage $stage
Pop-Location
}

task configure-frontend {
Push-Location serverless-ephemera
$ServerlessInfo = &"serverless" "info" "--stage" $stage | Out-String
Pop-Location

$ServerlessInfo -match 'POST - (?<url>.*/v2)' | Out-Null
$APIUrl = $Matches.url

Write-Verbose "Configuring frontend_config.js to reflect api url of '$APIUrl'";
Set-Content .\frontend\js\frontend_config.js -Value "`$.apiUrl = '$APIUrl';"
}

task deploy-s3bucketcontents {
$ConfigFile = Get-Content 'serverless-ephemera\config.yml' | Out-String
$Config = ConvertFrom-Yaml -Yaml $ConfigFile
$PublicFiles = Get-ChildItem frontend -Recurse | ?{!$_.psiscontainer}
foreach ($File in $PublicFiles) {
$RelativePath = $File.fullname -replace [Regex]::Escape($PSScriptRoot+'\frontend'), ''
Write-Verbose "Uploading $file to $RelativePath in $($config.public_bucket_name)"
Write-S3Object -BucketName $Config.public_bucket_name -File $file.fullname -Key $RelativePath -Region $Config.region -CannedACLName public-read
}

}

task test {
$testResultsFile = 'TestsResults.xml'
$testResults = Invoke-Pester -OutputFormat NUnitXml -OutputFile $testResultsFile -PassThru

if ($env:APPVEYOR){
Write-Host "Uploading test results to AppVeyor..."
(New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile))
}

if ($testResults.FailedCount -gt 0) {
$testResults | Format-List
Write-Error -Message 'One or more Pester tests failed. Build cannot continue!'
}
}

task destroy {
$ConfigFile = Get-Content 'serverless-ephemera\config.yml' | Out-String
$Config = ConvertFrom-Yaml -Yaml $ConfigFile

while(Get-S3Object -BucketName $config.public_bucket_name | Remove-S3Object -Force -region $config.region -BucketName $config.public_bucket_name){
Write-Host "Deleting objects from public s3 bucket..."
}

while(Get-S3Object -BucketName $config.private_bucket_name | Remove-S3Object -Force -region $config.region -BucketName $config.private_bucket_name){
Write-Host "Deleting objects from private s3 bucket..."
}

Push-Location serverless-ephemera
serverless remove --stage $stage
Pop-Location
}

44 changes: 27 additions & 17 deletions ephemera.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,42 +1,52 @@
if(!$stage){
if(!$stage){
$stage = 'dev'
}

# Get endpoint URL
Push-Location serverless-ephemera
$ServerlessInfo = &"serverless" "info" "--stage" $stage | Out-String
Pop-Location

$ConfigFile = Get-Content 'serverless-ephemera\config.yml' | Out-String
$ConfigFile = Get-Content 'config.yml' | Out-String
$Config = ConvertFrom-Yaml -Yaml $ConfigFile

$global:PublicWebsite = "http://{0}.s3-website-{1}.amazonaws.com" -f $config.public_bucket_name, $config.region

$global:addTextSecret = ($ServerlessInfo | select-string 'POST - (https://.*)').Matches[0].Groups[1].Value
$global:getTextSecret = ($ServerlessInfo | select-string 'GET - (https://.*)').Matches[0].Groups[1].Value
$global:PublicWebsite = "http://{0}.s3-website.{1}.amazonaws.com" -f $config.public_bucket_name, $config.region
$global:PublicWebsite = $global:PublicWebsite -replace "\`${self:custom.config.stage}", $config.stage
$ServerlessInfo -match 'POST - (?<url>.*/v2)' | Out-Null
$APIUrl = $Matches.url

Write-Verbose "Checking stage: $stage"
Write-Verbose "Checking API URL: $APIUrl"
Write-Verbose "Checking API URL: $addTextSecret and $getTextSecret"

Describe "Ephemera" {
It "Returns a valid secret url!" {
$global:TestSecret = "I am a secret, ssh!"
$Payload = @{secretText = $TestSecret} | ConvertTo-Json
$global:AddSecretResult = (Invoke-WebRequest -uri "$APIUrl/addTextSecret" -Body $Payload -UseBasicParsing -Method POST -ContentType "Application/JSON" -verbose).Content | ConvertFrom-Json
$AddSecretResult.Key | Should match '[\d\w]{8}-[\d\w]{4}-[\d\w]{4}-[\d\w]{4}-[\d\w]{12}'
$global:AddSecretResult = (Invoke-WebRequest -uri $global:addTextSecret -Body $Payload -UseBasicParsing -Method POST -ContentType "Application/JSON" -verbose).Content | ConvertFrom-Json
$global:AddSecretResult.Key | Should match '[\d\w]{8}-[\d\w]{4}-[\d\w]{4}-[\d\w]{4}-[\d\w]{12}'
}
It "Should return the correct secret!" {
$GetSecretResult = (Invoke-WebRequest -uri "$APIUrl/getSecret?key=$($AddSecretResult.Key)" -UseBasicParsing -Method GET -verbose).Content | ConvertFrom-Json
$GetSecretResult.body | Should Be $TestSecret
$Parameters = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
$Parameters['key'] = $global:AddSecretResult.Key
$Request = [System.UriBuilder]$global:getTextSecret
$Request.Query = $Parameters.ToString()
$GetSecretResult = ([char[]](Invoke-WebRequest -uri $request.uri -Method GET -UseBasicParsing -verbose).Content -join '') | ConvertFrom-Json
$GetSecretResult.secretText | Should Be $TestSecret
}
It "Should NOT return the correct secret twice!" {
$GetSecretResult = (Invoke-WebRequest -uri "$APIUrl/getSecret?key=$($AddSecretResult.Key)" -UseBasicParsing -Method GET -verbose).Content | ConvertFrom-Json
$GetSecretResult.body | Should Not Be $TestSecret

$Parameters = [System.Web.HttpUtility]::ParseQueryString([String]::Empty)
$Parameters['key'] = $global:AddSecretResult.Key
$Request = [System.UriBuilder]$global:getTextSecret
$Request.Query = $Parameters.ToString()
$GetSecretResult = ([char[]](Invoke-WebRequest -uri $request.uri -Method GET -UseBasicParsing -verbose).Content -join '') | ConvertFrom-Json
$GetSecretResult.secretText | Should Not Be $TestSecret
}
It "Should return a 200 on the public URL" {
(Invoke-WebRequest $global:PublicWebsite -UseBasicParsing).StatusCode | Should Be 200
}
It "Should have the API URL in the front end config js" {
(Invoke-WebRequest $global:PublicWebsite/js/frontend_config.js -UseBasicParsing).content | Should match $APIUrl
It "Should have the API URL in the front end config json" {

$Request = [System.UriBuilder]($global:PublicWebsite+"/js/config.json")
$GetConfig = Invoke-RestMethod -uri $request.uri -Method GET -UseBasicParsing
$GetConfig.apiUrl.length | should BeGreaterThan 0
}
}
6 changes: 2 additions & 4 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- The main JS for bringing it all together on this page -->
<script src="js/main.js"></script>
<!-- The config js that contains replacable values -->
<script src="js/frontend_config.js"></script>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
Expand All @@ -27,7 +25,7 @@
</nav>
<div class="jumbotron" id="pageSubtitle">
<div class="container text-center">

<h1>Ephemera</h1>
<h2>Secret Transfer</h2>
<p>Enter a text secret and you will be provided with a one-time use URL to give to the intended secret recipient.</p>
Expand All @@ -54,4 +52,4 @@ <h1>Secret URL</h1>

</div>
</body>
</head>
</head>
1 change: 0 additions & 1 deletion frontend/js/frontend_config.js

This file was deleted.

145 changes: 89 additions & 56 deletions frontend/js/main.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,105 @@
var valuesSet;
// Stolen from http://stackoverflow.com/questions/4656843/jquery-get-querystring-from-url
// Read a page's GET URL variables and return them as an associative array.
function getUrlVars() {
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for (var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
var vars = [],
hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for (var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}
var populateSecretURL = function (secretURL) {
$('#secretURL').text(secretURL);

// Clear out the secret field
$('#secret-text').val('');

// Slide down the output
$('#secretOutput').slideDown();
};
$(document).ready(function () {

urlVars = getUrlVars();
if (typeof urlVars != 'undefined' && urlVars.bucket) {

// if bucket is defined as a GET variable we've just uploaded something
populateSecretURL(window.location.origin + '?key=' + urlVars.key);
} else if(typeof urlVars != 'undefined' && window.location.href.match(/getSecret.html/)){
var populateSecretURL = function(secretURL) {
$('#secretURL').text(secretURL);

// Clear out the secret field
$('#secret-text').val('');

// if we don't have a secret key, but we're on getSecret.html, tell the user they can't do anything useful here
$('#secretOutput').html('<div class="alert alert-danger" role="alert"><strong>Error:</strong> No secret key defined. If you\'re trying to upload a secret, you may not have access to do so.</html>');

// Slide down the output
$('#secretOutput').slideDown();
};
$(document).ready(function() {

populateDom()
});

var populateDom = function() {
urlVars = getUrlVars();
if (typeof urlVars != 'undefined' && urlVars.bucket) {

} else if (typeof urlVars != 'undefined' && urlVars.key) {

// if bucket is defined as a GET variable we've just uploaded something
populateSecretURL(window.location.origin + '?key=' + urlVars.key);
} else if (typeof urlVars != 'undefined' && window.location.href.match(/getSecret.html/)) {

// if we don't have a secret key, but we're on getSecret.html, tell the user they can't do anything useful here
$('#secretOutput').html(`<div class="alert alert-danger" role="alert">
<strong>Error:</strong>
No secret key defined. If you\'re trying to upload a secret, you may not have access to do so.</html>
`);

// Slide down the output
$('#secretOutput').slideDown();

} else if (typeof urlVars != 'undefined' && urlVars.key) {
getSecret();
}

// Upload a string
$('#text-form').submit(uploadSecret);
}

var uploadSecret = function(ev) {
ev.preventDefault();
if ($('#secret-text').val().length == 0) {
return
}
$('#secret-text').prop('disabled', true);
$('#text-form > :submit').prop('disabled', true).val('Please Wait...');
getConfig(function(config) {
var post_addr = config['apiUrl'] + '/addTextSecret'
console.log(post_addr)
$.ajax({
url: post_addr,
method: 'POST',
data: JSON.stringify({
'secretText': $('#secret-text').val()
}),
contentType: "application/json"
}).done(function(result) {
$('#secret-text').prop('disabled', false);
$('#text-form > :submit').prop('disabled', false).val('Submit');
if (result.ErrorMessage) {
alert(result.ErrorMessage);
} else {
populateSecretURL(window.location.origin + '?key=' + result.key);
}
});
});
}

var getSecret = function() {
// if key is defined without bucket then we're retrieving a secret
var secretArea = $('#pageBody');
$('#pageSubtitle p').text('Displaying a secret once...');
secretArea.html('<h1>Loading Secret...</h1>');
$.get($.apiUrl + '/getSecret', { key: urlVars.key }, function (result) {
secretArea.html('<h1>Secret</h1>');
if (result.errorMessage) {
secretArea.append('Secret no longer exists');
} else if (result['Content-Type'] == 'text/plain') {
secretArea.append("<pre>"+result.body+"</pre>");
}
getConfig(function(config) {
$.get(config.apiUrl + '/getSecret', {
key: urlVars.key
}, function(result) {
secretArea.html('<h1>Secret</h1>');
if (result.message) {
secretArea.append(result.message);
} else {
secretArea.append($('<pre>').text(result.secretText));
}
}, 'json');
});
}

}

// Upload a string
$('#text-form').submit(function (ev) {
ev.preventDefault();
$.ajax({
url: $.apiUrl + '/addTextSecret',
method: 'post',
data: JSON.stringify({ 'secretText': $('#secret-text').val() }),
contentType: "application/json"

}).done(function (result) {
if(result.ErrorMessage){
alert(result.ErrorMessage);
} else {
populateSecretURL(window.location.origin + '?key=' + result.key);
}
});
});
});
var getConfig = function(callback) {
$.getJSON('js/config.json', callback)
}
Loading

0 comments on commit bef6291

Please sign in to comment.