GithubHelp home page GithubHelp logo

linkeys-app / signed-url Goto Github PK

View Code? Open in Web Editor NEW
22.0 3.0 6.0 317 KB

Enhanced signed URLs for Laravel, including attaching data, click limits and expiry.

License: MIT License

PHP 100.00%
laravel link expires signed-url

signed-url's Introduction

Laravel Signed URLs

Enhanced signed URLs for Laravel, including attaching data, click limits and expiry.

Build Status Code Coverage Scrutinizer Code Quality Total Downloads Licence

Table of Contents

Introduction

Revamped URL signing brought to Laravel. Pass data through any url securely, and control expiry and number of clicks.

  • Securely attach data to links to make it available to your controllers.
  • Create links of a limited lifetime.
  • Limit the number of clicks allowed on a link.
  • Limit the number of clicks on a group of links, such as only allowing one of a group of links to be clicked.

Version Compatibility

Laravel Url Signer
5.7.x 1.0.x
5.8.x 1.0.x
6.x 1.0.3
7.x 2.0.x
8.x 3.0.x
9.x 4.0.x
10.x 5.0.x

Getting Started

It couldn't be easier to install and set up url signing in your laravel app.

Installation

  1. Install through the composer package manager

    composer require linkeys/signed-url
    
  2. (Optional) Publish the assets

    php artisan vendor:publish --provider="Linkeys\UrlSigner\Providers\UrlSignerServiceProvider"
    

    You can also use the --tag=config or --tag=migrations flags to only publish the configuration files or the migrations.

  3. Run the migrations

    php artisan migrate

Usage

Overview

This package works by modifying a URL to give it a signature. This must be a url you have registered in your routes. When the signed URL is opened in the browser, our middleware will not only inject the attached data into a controller request, but throw exceptions if the link has expired, been clicked too many times or altered in any way.

Creating a Link

Standard Link

The easiest way to create a link is through the facade:

$link = \Linkeys\UrlSigner\Facade\UrlSigner::generate('https://www.example.com/invitation');
echo $link->getFullUrl(); // https://www.example.com/invitation?uuid=UUID

The link can now be sent out or used just like normal signed URLs. You may also use the sign method, which is simply an alias for generate, and the alias instead of importing the full facade namespace

$link = UrlSigner::sign('https://www.example.com/invitation');

You can also resolve an instance of \Linkeys\UrlSigner\Contracts\UrlSigner from the container and call the facade functions directly.

Data

Instead of encoding data into the url yourself, simply pass it as the second argument.

$link = \Linkeys\UrlSigner\Facade\UrlSigner::generate('https://www.example.com/invitation', ['foo' => 'bar']);
echo $link->getFullUrl(); // https://www.example.com/invitation?uuid=UUID

In your controller, e.g. InvitationController.php

echo $request->get('foo');  // bar

Through this method, the data can't be altered by anyone and can't even be seen by users, securing your application and hiding implementation details.

Expiry

Additional to a basic link is the ability to set the expiry of the link. Only want a link to be available for 24 hours?

$link = \Linkeys\UrlSigner\Facade\UrlSigner::generate('https://www.example.com/invitation', ['foo' => 'bar'], '+24 hours');

The expiry accepts a string, unix timestamp or a datetime instance (i.e. Carbon).

Click Limit

The number of clicks of a link can also be set. If you only want a user to be able to click a link one time:

$link = \Linkeys\UrlSigner\Facade\UrlSigner::generate('https://www.example.com/invitation', ['foo' => 'bar'], '+24 hours', 1);

The first time the link is clicked, the route will work like normal. The second time, since the link only has a single click, an exception will be thrown. Of course, passing null instead of '+24 hours' to the expiry parameter will create links of an indefinite lifetime.

Link Groups

By grouping links, the click limit may be spread across multiple links. Given a group with a click limit of 2 but 3 links will only allow two total clicks. Expiry is default for links unless they specify it themselves.

    $group = \Linkeys\UrlSigner\Facade\UrlSigner::group(function($links) {
        $links->generate('https://www.example.com', ['foo'=>'bar']),
        $links->generate('https://www.example.com', ['foo'=>'baz'])
    }, '+ 24 hours', 1)'

This will create two links, both with different data and expiring in 24 hours, but since the group click limit is 1 only a single link may be clicked. This is useful for situations in which you want to give the user a choice of links to click, such as for an invitation (the user should only be able to click 'Yes' or 'No' to respond).

The expiry parameter in a group is a default applied to all links in the group which don't have an expiry date. If you set the expiry date in the link in addition to the in the group, only the link expiry will be checked.

You can access the links using $group->links;, which wll return a Laravel collection.

Handling a Link

Middleware

To enable exceptions to be thrown when a link is invalid in any way, simply add the 'link' middleware to any relevant routes. This will take care of:

  • Ensuring the URL has not been changed.
  • Checking the link has not expired.
  • Checking the link hasn't reached the limit on number of clicks.
  • Adding the link and relevant data into the request.

Retrieving Link Information

We automatically put all data attached to a link into the request attributes. The following is a controller method called when a user clicks on a generated link with data attached.

public function acceptInvitation(Request $request)
{
    // Given the link has the data ['foo' => 'bar']...
    $bar = $request->get('foo');
    
    // To retrieve the link instance:
    $link = $request->get(\Linkeys\UrlSigner\Models\Link::class);
    var_dump($link->data)  // ['foo' => 'bar']
}

Error handling

The middleware will throw exceptions when the link is invalid in any way. By default, these will be handled by Laravel to display an error page with a simple message.

By using your apps exception handler, you can choose to show custom pages to respond to each of the following situations by catching an error and returning a response:

Condition Exception
URL has been changed LinkNotFoundException
Signature was not found LinkNotFoundException
Link has been clicked too many times LinkClickLimitReachedException
Link group has been clicked too many times LinkGroupClickLimitReachedException
Link has expired LinkExpiredException
Link group has expired and link had no expiry LinkGroupExpiredException

Through using exception inheritance, you can control which pages to show for which exception, or just have a single page for all link exceptions. The inheritance diagram is shown below, with all classes being in the namespace \Linkeys\UrlSigner\Exceptions except for \Exception.

Exception Inheritance

Examples

Coming soon...

Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated. See CONTRIBUTING.md for more information.

  1. Fork the Project

  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)

  3. Commit your Changes (git commit -m 'Add some AmazingFeature)

  4. Push to the Branch (git push origin feature/AmazingFeature)

  5. Open a pull request

License

Distributed under the MIT License. See LICENSE for more information.

Contact

Toby Twigger - [email protected]

Project Link: https://github.com/linkeys-app/signed-url

signed-url's People

Contributors

tobytwigger avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

signed-url's Issues

Register Alias

What will the feature achieve?
Allow for easier access to the Facade through use of an alias

Why is this necessary?
This will remove the need to import the facade.

How should the feature work
The package should automatically register an alias for the facade

** Will this affect any other features**
No, since the alias is just another way to use the facade and the facade can still be used.

Reduce middleware dependence

Currently, the middleware must be run in the order

\Linkeys\UrlSigner\Middleware\AddLinkToRequest,
\Linkeys\UrlSigner\Middleware\AddLinkDataToRequest,
\Linkeys\UrlSigner\Middleware\CheckLinkUnchanged,
\Linkeys\UrlSigner\Middleware\CheckLinkValid

This could cause an issue since middleware order cannot be enforced.

Suggested Solution:

  • Resolve the link from the request in AddLinkDataToRequest, CheckLinkUnchanged, CheckLinkValid to remove dependency on AddLinkToRequest.

HTTPS not working

Summary of the bug
if the URL stored in https it cannot be used and become 404. but if you stored in http, it working.

To Reproduce
store the URL in https:// and you will try to open the pages with the middleware you will get 404.

Suggestion of a fix
maybe can make it check both HTTP and HTTPS.

Clean Service Provider

The service provider has a lot going on in single methods. These should be extracted to class methods in sensible groups.

Use sign instead of generate

What will the feature achieve?
Allow a link to be created using ::sign instead of ::generate

Why is this necessary?
::sign is cleaner and easier to remember than generate

How should the feature work?
A sign method should be declared in the UrlSigner, acting as an alias for the generate method

Will this affect any other features?
No, since sign will just be an alias and not change generate in any way.

Any other information?

Change link request binding to Link::class

What will the feature achieve?
Free up the 'link' key in the request.

Why is this necessary?
The link model is made available through $request->get('link');, but this may be needed by a user since it is a common word.

How should the feature work?
The link model should instead be bound to the class name, e.g. \Linkeys\UrlSigner\Models\Link::class. This is much less likely to be needed by an individual

Will this affect any other features?
Anywhere 'link' is used should be replaced but no other feature will be affected.

Any other information?

Rename facade

The facade should potentially be renamed to UrlSigner to keep consistency throughout the project

Removing/Reusing old links

What will the feature achieve?
Allow reusing existing records in the links table where the request previously existed or some process to delete old link from the links table.

Data not accessible in controller

Summary of the bug
Data is not accessible in controller

To Reproduce

  1. Create a link that contains some data
  2. Click on the link
  3. Try and access the data using $request->get('data');

Expected behaviour
The value of the 'data' key to be returned.

Actual behaviour
null was returned.

Additional context
The link is accessible in $request->get('link');. It's just the data left.

Suggestion of a fix
Add a new middleware to inject the data into the request, not just the link.

Laravel 10 support

What will the feature achieve?
Allow the upgrade of the Laravel framework to version 10

Why is this necessary?
Upgrade to Laravel 10

Foreign Keys in Table

The link table migration should optionally create foreign key constraints between the links and groups tables

spatie/url dependency

Any chance to update the spatie/url dependency in order to support recent versions of PHP?

╰─ composer require linkeys/signed-url                                                                                         ─╯
Using version ^5.0 for linkeys/signed-url

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - spatie/url 1.3.0 requires php ^7.0 -> your php version (8.2.5) does not satisfy that requirement.
    - spatie/url[1.3.1, ..., 1.3.3] require php ^7.2 -> your php version (8.2.5) does not satisfy that requirement.
    - spatie/url[1.3.4, ..., 1.3.5] require psr/http-message ^1.0 -> found psr/http-message[1.0, 1.0.1, 1.1] but the package is fixed to 2.0 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.
    - linkeys/signed-url v5.0.0 requires spatie/url ^1.3 -> satisfiable by spatie/url[1.3.0, ..., 1.3.5].
    - Root composer.json requires linkeys/signed-url ^5.0 -> satisfiable by linkeys/signed-url[v5.0.0].

I am using laravel 5.6. So when I install this package, getting follwing error "linkeys/signed-url v1.0.0 requires illuminate/config ~5.7.0|~5.8.0 "

What will the feature achieve?
e.g. Allow a user to [...]

Why is this necessary?
Why do you think this is a good idea? Who will it help?

How should the feature work?
Try and include as much information as you can about how you'd like the feature to be used.

Will this affect any other features?
e.g. This will replace the method [...]

Any other information?

Invalid alias defined in composer.json

Summary of the bug
This namespace is invalid and class does not exist which causes PHP warnings

"UrlSigner": "Linkeys\\UrlSigner\\Link"

PHP warning:

PHP Warning:  Class 'Linkeys\UrlSigner\Link' not found in .\vendor\laravel\framework\src\Illuminate\Foundation\AliasLoader.php on line 80

To Reproduce
Run

UrlSigner::sign('https://www.example.com/invitation')->url;

in Laravel Tinker after installing package and watch PHP warning appear.

Expected behaviour
No PHP warning.

Actual behaviour
PHP warning thrown.

Suggestion of a fix
Fix alias definition or remove, it doesn't seem like it's needed.

Package completely broken? Examples don't work.

Summary of the bug
I installed this package and ran the example:

$link = \Linkeys\UrlSigner\Facade\UrlSigner::generate('https://www.example.com/invitation');
echo $link; // https://www.example.com/invitation?uuid=UUID

This returns the Link instance as a JSON string. And not the URL to use as shown in the example.

To Reproduce
Install package in Laravel 8 project, run:

$link = \Linkeys\UrlSigner\Facade\UrlSigner::generate('https://www.example.com/invitation');
echo $link; // https://www.example.com/invitation?uuid=UUID

and watch as it gives you a JSON string.

Expected behaviour
Return string as shown.

Actual behaviour
Returns JSON string.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.