Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LABS] feat: adapter that wraps strategy #3056

Merged
merged 1 commit into from
Jun 17, 2019

Conversation

jannyHou
Copy link
Contributor

@jannyHou jannyHou commented Jun 6, 2019

Connect to #2311

This PR contains 2 potential solutions(you will find TWO adapter files in src + TWO unit tests + TWO acceptance tests) for converting the passport based strategies to the one defined in @loopback/authentication:

  • wrap the passport strategy, like BasicStrategy in passport-http: the 1st commit
  • is supposed to to wrap the passport middleware, but due to the type error I explained in 561f6b5#diff-fb7bcb47eeb0d22961f9f511534a7db6R76, it still takes in a strategy in the constructor
    • so my code is more like a PoC that proves invoking passport.authenticate() works
    • if we could solve the type error, I can quickly turn it into the expected adapter
    • if the type error cannot be solved then we have to wrap the strategy not the middleware

Let's discuss which one do we want and discard the unwanted one. If both are good, I will extract one of them to a new package.


Update:

We decided to take the simple approach: wrapping the strategy, so please ignore the 2nd proposal above ^

Checklist

👉 Read and sign the CLA (Contributor License Agreement) 👈

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • API Documentation in code was updated
  • Documentation in /docs/site was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in examples/* were updated

👉 Check out how to submit a PR 👈

@jannyHou jannyHou mentioned this pull request Jun 6, 2019
7 tasks
@jannyHou jannyHou self-assigned this Jun 6, 2019
}

function verify(username: string, password: string, cb: Function) {
process.nextTick(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to wrap it into process.nextTick()? The users.find is already async.


// FOR REVIWERS: THIS ACCEPTANCE TEST IS FOR `PassportAdapter`

import { authenticate, AuthenticateFn, AuthenticationBindings, AuthenticationComponent, AuthenticationStrategy, UserProfile } from '@loopback/authentication';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code does not follow prettier formatting style.

convertToAuthStrategy(basic: BasicStrategy): AuthenticationStrategy {
return new StrategyAdapter(basic, AUTH_STRATEGY_NAME);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the following idea?

  1. Have one PassportAuthenticationStrategy provider to be registered with LB4 authentication strategies extension point.

  2. The PassportAuthenticationStrategy supports passport:<name>

  3. Inside PassportAuthenticationStrategy, we expose another extension point to register all passport strategies (for example, with a tag passport.strategy).

    • Creates one instance of passport
    • Discovers all passport strategy extensions
    • Calls passport.use() to register all passport strategies

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raymondfeng Having 2 extensions kind of falls back to the initial implementation in the draft PR #2822 (which unfortunately was squashed and cannot be recovered...)

The reason why I wrapped the passport strategy directly to a common implementation of AuthenticationStrategy is explained in `dbc51ce#r280376757

Copy link
Contributor Author

@jannyHou jannyHou Jun 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I am able to perform an authentication by invoking passport.authenticate(), but not quite sure about the benefit of doing it (compared with invoking strategy.authenticate()), and describing the type of a passport instance is very problematic, see explanation
By saying that I didn't mean we have to define the passport in any function's parameter, but in case we have to do that, we need figure out a solution for the type issue.

@bajtos bajtos changed the title feat: adapter that wraps strategy [LABS] feat: adapter that wraps strategy Jun 7, 2019
@@ -0,0 +1,25 @@
Copyright (c) IBM Corp. 2017,2019. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to name the package as authentication-passport?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to use authentication-passport

@@ -0,0 +1,25 @@
Copyright (c) IBM Corp. 2017,2019. All Rights Reserved.
Node module: @loopback/authentication
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change the module name here.

.tag({
[CoreTags.EXTENSION_FOR]:
AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should have a sugar method such as usePassportStrategy() here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import {StrategyAdapter} from `@loopback/passport-adapter`;
import {AuthenticationStrategy} from '@loopback/authentication';

class PassportBasicAuthProvider implements Provider<AuthenticationStrategy> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to use StrategyAdapter as the base class.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@raymondfeng Hmm is there a big difference between using the provider or using a class that extends StrategyAdapter?

import {AuthenticationStrategy} from '@loopback/authentication';

class PassportBasicAuthProvider implements Provider<AuthenticationStrategy> {
constructor(@inject(VERIFY) verifyFn: BasicVerifyFunction);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is VERIFY constant defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops...

namespace:
AuthenticationBindings.AUTHENTICATION_STRATEGY_EXTENSION_POINT_NAME,
},
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this as not very elegant. Can we find a way how to simplify this registration step? For example, can we leverage @extension or @bind decorators to specify the extension point name in the code where we are defining PassportBasicAuthProvider.

@raymondfeng you are the author of addExtension and the extension-point/extension implementation. What is the recommended way for registering extensions? Are we missing any new helpers in @loopback/core to simplify this process?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My code is out of date we do have that sugar function :)

#3056 (comment)

@@ -0,0 +1,25 @@
Copyright (c) IBM Corp. 2017,2019. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be 2019.

@@ -23,7 +23,7 @@ async function markNonLabsPackagesPrivate() {
const dir = path.relative(pkg.rootPath, pkg.location);
if (dir.startsWith('labs')) continue;
const data = readJsonFile(pkg.manifestLocation);
let modified = !data.private;
const modified = !data.private;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update the base branch with this change first?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! updated in #3150

@@ -0,0 +1,185 @@
# Passport Strategy Adapter

_Important: We suggest users understand LoopBack's authentication system[Link
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We strongly recommend that users ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And I updated the link as well since #2977 get merged

TBD](some loopback.io link) before using this module_

This is an adapter module created for plugging in
[`passport`](https://www.npmjs.com/package/passport) base strategies to the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base -> based

## Installation

```sh
npm i @loopback/passport-adapter --save
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@loopback/authentication-passport

@jannyHou
Copy link
Contributor Author

local tests pass

 [email protected] mocha /Users/jannyhou/2019/auth/adapter/loopback-next
> node packages/build/bin/run-mocha "labs/*/dist/__tests__/**/*.js"



  ․․․․․․․

  7 passing (90ms)


> [email protected] posttest /Users/jannyhou/2019/auth/adapter/loopback-next
> npm run lint


> [email protected] lint /Users/jannyhou/2019/auth/adapter/loopback-next
> npm run prettier:check && npm run eslint && node bin/check-package-locks


> [email protected] prettier:check /Users/jannyhou/2019/auth/adapter/loopback-next
> npm run prettier:cli -- -l


> [email protected] prettier:cli /Users/jannyhou/2019/auth/adapter/loopback-next
> node packages/build/bin/run-prettier "labs/**/*.ts" "labs/**/*.js" "labs/**/*.md" "-l"


> [email protected] eslint /Users/jannyhou/2019/auth/adapter/loopback-next
> node packages/build/bin/run-eslint --report-unused-disable-directives --cache .


## Usage

### Simply Usage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple Usage or Basic Use

@emonddr
Copy link
Contributor

emonddr commented Jun 14, 2019

@jannyHou

Created an instance of the passport strategy

Create an instance of the passport strategy

@emonddr emonddr self-requested a review June 14, 2019 17:22
@emonddr
Copy link
Contributor

emonddr commented Jun 14, 2019

@jannyHou

Take the basic strategy exported from passport-http as an example, first create an instance of the basic strategy with your verify function

Taking the basic strategy exported from passport-http as an example, first create an instance of the basic strategy with your verify function

@jannyHou
Copy link
Contributor Author

jannyHou commented Jun 14, 2019

For reviewers: the decreased code coverage is complaining about the packages/* modules, so I guess the coverage check script on branch labs/base need to be fixed, I am going to submit a PR for it.

see report in https://coveralls.io/builds/24001707

This is an adapter module created for plugging in
[`passport`](https://www.npmjs.com/package/passport) base strategies to the
authentication system in `@loopback/[email protected]`.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see two comments I added today, June 14 outside this review.

It's the similar configuration as you do when adding a strategy to a `passport`
by calling `passport.use()`.

But don't provide any passport specific options since we don't support the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But don't provide any passport specific options since we don't support the session manager.

Can you give an example of what you mean here? And what happens if that passport strategy requires a session manager? Will it not work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I doubled checked the usage of passport-http, and realize the passport specific options are only provided when the strategy is invoked by passport.authenticate(), like https://github.com/jaredhanson/passport-http#authenticate-requests.
So, since we don't involve passport in this PR, we don't introduce that concern either. I will remove "But don't provide any passport specific options since we don't support the session manager."

// Give the strategy a name
// You'd better define your strategy name as a constant, like
// `const AUTH_STRATEGY_NAME = 'basic'`.
// So that you will decorate the APIs later with the same name.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// So that you will decorate the APIs later with the same name.

// You will need to decorate the APIs later with the same name

import {AuthenticationBindings} from '@loopback/authentication';

app
.bind('authentication.strategies.basicAuthStrategy')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.bind('authentication.strategies.basicAuthStrategy')

Is there a constant that can be used from @authenticate to form the first two segments of this string?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the class name of the strategy here

https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/types.ts#L65

is appended as the last segment of the string?

Copy link
Contributor Author

@jannyHou jannyHou Jun 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@emonddr The key itself could be any name, it is the tag that makes the strategy recognized by the extension point, and the name property defined in the strategy class that needs to match the name in decorator @authenticate('basic')

I don't have a strong opinion on using a string or leveraging the constant in https://github.com/strongloop/loopback-next/blob/master/packages/authentication/src/keys.ts#L108, I would respect your preference.


### With Provider

If you need to inject stuff (e.g. the verify function) when configure the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you need to inject stuff (e.g. the verify function) when configure the
strategy, you may want to provide your strategy as a provider.

If you need to inject stuff (e.g. the verify function) when configuring the
strategy, you may want to supply your strategy as a provider.

If you need to inject stuff (e.g. the verify function) when configure the
strategy, you may want to provide your strategy as a provider.

_Note: If you are not familiar with LoopBack provider, check the documentations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: If you are not familiar with LoopBack provider, check the documentations
in

Note: If you are not familiar with LoopBack providerS, check the documentation
in

// Applies the `StrategyAdapter` to the configured basic strategy instance.
// You'd better define your strategy name as a constant, like
// `const AUTH_STRATEGY_NAME = 'basic'`
// So that you will decorate the APIs later with the same name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// So that you will decorate the APIs later with the same name

// You will decorate the APIs later with the same name

Copy link
Contributor

@b-admike b-admike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got some comments, but looks good to me overall 👍

labs/authentication-passport/README.md Outdated Show resolved Hide resolved
first create an instance of the basic strategy with your `verify` function.

```ts
function verify(username: string, password: string, cb: Function) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: would it help to show the import statement here?

labs/authentication-passport/README.md Show resolved Hide resolved
labs/authentication-passport/README.md Outdated Show resolved Hide resolved
labs/authentication-passport/README.md Outdated Show resolved Hide resolved
labs/authentication-passport/README.md Outdated Show resolved Hide resolved
@inject('authentication.basic.verify') verifyFn: BasicVerifyFunction,
);
value(): AuthenticationStrategy {
const basicStrategy = this.configuredBasicStrategy(verify);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.configuredBasicStrategy(verify); -> this.configuredBasicStrategy(verifyFn);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyFn is a type, verify is the real function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh...where do we get verify from? I thought we are passing the injected variable here https://github.com/strongloop/loopback-next/pull/3056/files#diff-ac3170e38ad0d9561109e8d873e5e467R122. I thought verifyFn is the function and BasicVerifyFunction is the type.

// `const AUTH_STRATEGY_NAME = 'basic'`
// So that you will decorate the APIs later with the same name
convertToAuthStrategy(basic: BasicStrategy): AuthenticationStrategy {
return new StrategyAdapter(basic, AUTH_STRATEGY_NAME);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we show where AUTH_STRATEGY_NAME is declared or imported?

}

app.bind('authentication.basic.verify').to(verify);
registerAuthenticationStrategy(app, PassportBasicAuthProvider);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we include registerAuthenticationStrategy and PassportBasicAuthProvider imports at the top for this line?

Copy link
Contributor

@b-admike b-admike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

// FOR REVIWERS: THIS UNIT TEST IS FOR `StrategyAdapter`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: REVIWERS -> REVIEWERS

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh..another good catch, that comment should be moved too!

@jannyHou jannyHou merged commit fac3ae7 into labs/passport-adapter Jun 17, 2019
@delete-merged-branch delete-merged-branch bot deleted the labs-dev/passport-adapter branch June 17, 2019 04:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants