Security Advisories (9)
CVE-2020-11022 (2020-04-29)

In jQuery versions greater than or equal to 1.2 and before 3.5.0, passing HTML from untrusted sources - even after sanitizing it - to one of jQuery's DOM manipulation methods (i.e. .html(), .append(), and others) may execute untrusted code. This problem is patched in jQuery 3.5.0.

CVE-2020-11023 (2020-04-29)

In jQuery versions greater than or equal to 1.0.3 and before 3.5.0, passing HTML containing <option> elements from untrusted sources - even after sanitizing it - to one of jQuery's DOM manipulation methods (i.e. .html(), .append(), and others) may execute untrusted code. This problem is patched in jQuery 3.5.0.

CVE-2019-11358 (2019-04-20)

jQuery before 3.4.0, as used in Drupal, Backdrop CMS, and other products, mishandles jQuery.extend(true, {}, ...) because of Object.prototype pollution. If an unsanitized source object contained an enumerable __proto__ property, it could extend the native Object.prototype.

CVE-2015-9251 (2018-01-18)

jQuery before 3.0.0 is vulnerable to Cross-site Scripting (XSS) attacks when a cross-domain Ajax request is performed without the dataType option, causing text/javascript responses to be executed.

CVE-2011-4969 (2013-03-08)

Cross-site scripting (XSS) vulnerability in jQuery before 1.6.3, when using location.hash to select elements, allows remote attackers to inject arbitrary web script or HTML via a crafted tag.

CVE-2012-6708 (2018-01-18)

jQuery before 1.9.0 is vulnerable to Cross-site Scripting (XSS) attacks. The jQuery(strInput) function does not differentiate selectors from HTML in a reliable fashion. In vulnerable versions, jQuery determined whether the input was HTML by looking for the '<' character anywhere in the string, giving attackers more flexibility when attempting to construct a malicious payload. In fixed versions, jQuery only deems the input to be HTML if it explicitly starts with the '<' character, limiting exploitability only to attackers who can control the beginning of a string, which is far less common.

CVE-2020-7656 (2020-05-19)

jquery prior to 1.9.0 allows Cross-site Scripting attacks via the load method. The load method fails to recognize and remove "<script>" HTML tags that contain a whitespace character, i.e: "</script >", which results in the enclosed script logic to be executed.

CVE-2019-5428

Prototype Pollution is a vulnerability affecting JavaScript. Prototype Pollution refers to the ability to inject properties into existing JavaScript language construct prototypes, such as objects. JavaScript allows all Object attributes to be altered, including their magical attributes such as _proto_, constructor and prototype. An attacker manipulates these attributes to overwrite, or pollute, a JavaScript application object prototype of the base object by injecting other values. Properties on the Object.prototype are then inherited by all the JavaScript objects through the prototype chain. When that happens, this leads to either denial of service by triggering JavaScript exceptions, or it tampers with the application source code to force the code path that the attacker injects, thereby leading to remote code execution.

CVE-2014-6071 (2018-01-16)

jQuery 1.4.2 allows remote attackers to conduct cross-site scripting (XSS) attacks via vectors related to use of the text method inside after.

NAME

Yancy::Help::Auth - How to do user authentication and authorization in Yancy

VERSION

version 1.049

DESCRIPTION

This document describes how to use Yancy auth plugins for authentication and authorization.

AUTHENTICATION

Authentication is determining who a user is or verifying a user is who they say they are. Sometimes this means a username and password. Sometimes this means asking another website to verify the user over a secure channel.

In Yancy, users are represented as a row in a schema, like this schema which uses an E-mail address and a password to authenticate a user:

use Mojolicious::Lite;
plugin Yancy => {
    backend => 'sqlite:myapp.db',
    schema => {
        # CREATE TABLE users ( email VARCHAR PRIMARY KEY, password VARCHAR NOT NULL );
        users => {
            'x-id-field' => 'email',
            required => [ 'email', 'password' ],
            properties => {
                email => {
                    type => 'string',
                    format => 'email',
                },
                password => {
                    type => 'string',
                    format => 'password',
                },
            },
        },
    },
};

Once we have a place to store our users, I can configure my authentication method. In this instance, I want to use the Yancy::Plugin::Auth::Password authentication plugin. I need to configure the plugin with what schema to use for users, what fields to use for the user's name and password, and what type of password digest to use (which determines how securely the password is stored).

app->yancy->plugin( 'Auth::Password' => {
    schema => 'users',
    username_field => 'email',
    password_field => 'password',
    password_digest => {
        type => 'SHA-1',
    },
} );

Once I configure an authentication plugin, Yancy will automatically secure the content editor to require an authenticated user. In the "AUTHORIZATION" section, I will explain how to change which users can use the editor.

So, in order to get to the editor after I've configured my auth plugin, I must create a user for myself to use. I can do this on the command-line using Mojolicious's eval command and the Yancy create helper.

$ ./myapp.pl eval 'app->yancy->create(
    users => {
        email => "doug\@preaction.me",
        password => "123qwe",
    }
)'

Now I can log in to Yancy and use the editor, while forbidding any random visitor to my site from being able to change it.

Multiple Authentication Methods

It's common for modern websites to allow users multiple ways to verify their identity. Yancy allows configuring multiple authentication methods using the Yancy::Plugin::Auth plugin.

I want to add Github authentication to my site. First, I need to add a column to store the user's Github handle, so I know who they are when they come back. Notice this time that none of the columns in my users table are required, since a user may have either Password auth or Github auth (or both!)

use Mojolicious::Lite;
plugin Yancy => {
    backend => 'sqlite:myapp.db',
    schema => {
        # CREATE TABLE users (
        #   email VARCHAR UNIQUE,
        #   github_account VARCHAR UNIQUE,
        #   password VARCHAR
        # );
        users => {
            'x-id-field' => 'email',
            properties => {
                email => {
                    type => 'string',
                    format => 'email',
                },
                password => {
                    type => 'string',
                    format => 'password',
                },
                github_account => {
                    type => [ 'string', 'null' ],
                },
            },
        },
    },
};

With my new users table ready, I can configure my auth plugins. The Yancy::Plugin::Auth plugin takes an array of other auth plugins:

app->yancy->plugin( 'Auth' => {
    # Configuration common to all plugins can be set once globally
    schema => 'users',
    # Here are the individual auth plugins to configure
    plugins => [
        [ Password => {
            username_field => 'email',
            password_field => 'password',
            password_digest => {
                type => 'SHA-1',
            },
        } ],
        [ Github => {
            username_field => 'github_account',
            # Get a client ID and secret by creating an app at
            # https://github.com/settings/applications/new
            client_id => 'dc09a416a5aee52c7e1f',
            client_secret => '97d1411bafd3ab3193a5fd2e18504ff2ac814a43',
        } ],
    ]
} );

Now I can create a user with a Github account and a password and authenticate myself either way:

$ ./myapp.pl eval 'app->yancy->create(
    users => {
        email => "doug\@preaction.me",
        password => "123qwe",
        github_account => "preaction",
    }
)'

AUTHORIZATION

Once a user is authenticated, authorization is what they are allowed to do. By default, the only authorization level is "Is this user logged-in?". On a site with a lot of users, this is not enough: We need to be able to control who is allowed to do what on the site.

The way Yancy provides to authorize users is the yancy.auth.require_user helper. This helper returns a subroutine suitable to be used in Mojolicious's under route.

# Require a user is logged-in
my $is_logged_in = app->yancy->auth->require_user;

# Create a user area of the site
my $user_route = app->routes->under( '/user', $is_logged_in );
$user_route->get( '' )->to( 'user#home' );
$user_route->get( 'profile' )->to( 'user#edit_profile' );
$user_route->post( 'profile' )->to( 'user#save_profile' );

Creating an Admin User

To further restrict areas of the site to only certain users, I need to add another column to my users schema. This time, I will add an is_admin column to mark users who should get access to the Yancy editor.

use Mojolicious::Lite;
plugin Yancy => {
    backend => 'sqlite:myapp.db',
    schema => {
        # CREATE TABLE users (
        #   email VARCHAR UNIQUE,
        #   github_account VARCHAR UNIQUE,
        #   is_admin BOOLEAN DEFAULT FALSE,
        #   password VARCHAR
        # );
        users => {
            'x-id-field' => 'email',
            properties => {
                email => {
                    type => 'string',
                    format => 'email',
                },
                password => {
                    type => 'string',
                    format => 'password',
                },
                github_account => {
                    type => [ 'string', 'null' ],
                },
                is_admin => {
                    type => 'boolean',
                    default => 0,
                },
            },
        },
    },
};

With my new field added, I can update my user to set the is_admin flag:

$ ./myapp.pl eval 'app->yancy->set(
    users => "doug\@preaction.me", {
        is_admin => 1,
    }
)'

And configure the editor to require the is_admin flag to be set:

use Mojolicious::Lite;
plugin Yancy => {
    backend => 'sqlite:myapp.db',
    schema => { ... },
    editor => {
        require_user => { is_admin => 1 },
    },
};

Now only admins have access to the editor.

Allow User Registration

Now that I've restricted my editor to administrators, I can allow users to register new accounts for themselves. I do this by adding allow_register => 1 to my auth configuration:

app->yancy->plugin( 'Auth' => {
    schema => 'users',
    allow_register => 1,
    plugins => [ ... ],
} );

Now when a user is not logged-in, they will see a button to register for the site. Or they can click on the "Login with Github" button to create their account.

TODO

There are parts of this that could be made simpler:

  • A role-based authorization control (RBAC) plugin could make more complex authorization schemes easier

  • The Github auth should pre-configure itself using Github App Manifests

  • Add authorization configuration in additional places, such as schema and schema properties. Add permissions to view and/or edit (edit implies view). Users without 'view' permission should not see the schema/property in the editor by any means (but may be able to see the data on another page).

SEE ALSO

The full example application Yancy::Plugin::Auth

AUTHOR

Doug Bell <preaction@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2019 by Doug Bell.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.