AdonisJS 'bouncer' package not working as intended - adonis.js

As described here, I have implemented the authorization:
start/bouncer.ts:
import Bouncer from '#ioc:Adonis/Addons/Bouncer'
export const { actions } = Bouncer
export const { policies } = Bouncer.registerPolicies({
UserPolicy: () => import('App/Policies/UserPolicy'),
})
app/Policies/UserPolicy.ts:
import { BasePolicy } from '#ioc:Adonis/Addons/Bouncer'
import User from 'App/Models/User'
export default class UserPolicy extends BasePolicy {
public async before(user?: User) {
return user?.isSuperUser
}
public async list(user: User) {
await user.load('policies')
return user.policies.some((policy) => policy.identifier === 'user:list')
}
// ...
}
resources/vires/layouts/main.edge
#can('UserPolicy.list')
<p>Can see users list</p>
#endcan
And I cannot see the paragraph. In fact, I placed console.log inside the action, but it didn't get executed. I don't know if I'm missing anything. Can anyone shed some lights onto it?

Gotcha! This says:
The actual action callback is never executed when a before hook returns a true or a false value.
Make sure to return undefined if you want the bouncer to execute the next hook or the action callback.
These 2 statements were missed out. :)

Related

Unit Testing Passport Strategy / NestJS Guard

I have a JWTGuard() decorator which follows the implementation as described in https://docs.nestjs.com/security/authentication#jwt-functionality
It looks like so:
import { ExecutionContext, Injectable } from '#nestjs/common';
import { Reflector } from '#nestjs/core';
import { AuthGuard } from '#nestjs/passport';
import { IS_PUBLIC_ROUTE } from '../decorators/public-route.decorator';
#Injectable()
export class JWTAuthenticationGuard extends AuthGuard('jwt') {
constructor(private readonly reflector: Reflector) {
super();
}
canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(
IS_PUBLIC_ROUTE,
[context.getHandler(), context.getClass()],
);
if (isPublic) return true;
return super.canActivate(context)
}
}
It works as expected, but having difficulty unit testing the guard. In short, I want to test three things:
If IS_PUBLIC_ROUTE is set, then canActivate() returns true ✅ this unit test works
If an expired/invalid/incorrectly signed token is passed, canActivate() returns false ❌
If a valid token is found, canActivate() returns true ❌
I tried to follow https://github.com/jmcdo29/testing-nestjs/blob/master/apps/complex-sample/src/cat/cat.guard.spec.ts which just tests a bare guard, not extending a Passport Guard.
Tests are setup as follows:
let guard: JWTAuthenticationGuard;
const reflector = new Reflector();
beforeEach(() => {
guard = new JWTAuthenticationGuard(reflector);
passport.use('jwt', new JWTStrategy());
});
The first test (should return false)
it('should return false if no authorization token is found', () => {
const context = createMock<ExecutionContext>();
const result = guard.canActivate(context);
expect(result).toBe(false);
});
My test never even makes it to the expect() clause, as it has thrown an UnauthorisedException and the test suite exits entirely.
I tried debugging inside the canActivate() method, and checking the type of super.canActivate() which tells me that it's a pending promise. I tried awaiting it, but still says its Pending, I tried wrapping in a try/catch, but the catch block never runs. Reading through the code of passport's AuthGuard, it never throws either.
I tried to add a valid token:
it('should return false if no authorization token is found', async () => {
const context = createMock<ExecutionContext>();
expect(context.switchToHttp()).toBeDefined();
context.switchToHttp().getRequest.mockReturnValue({
headers: {
Authorization:
'Bearer ey...[truncated]...dE',
},
});
const result = await guard.canActivate(context);
expect(result).toBe(true);
however I still just get an UnauthorisedException thrown from god knows where at the result = guard.can... line, and still never makes it to the expect.
Any guidance would be appriciated.

Dart Testing with Riverpod StateNotifierProvider and AsyncValue as state

This is my first app with Dart/Flutter/Riverpod, so any advice or comment about the code is welcome.
I'm using Hive as embedded db so the initial value for the provider's state is loaded asynchronously and using an AsyncValue of riverpod to wrapped it.
The following code works but I've got some doubts about the testing approach, so I would like to confirm if I'm using the Riverpod lib as It supposed to be used.
This is my provider with its deps (Preferences is a HiveObject to store app general config data):
final hiveProvider = FutureProvider<HiveInterface>((ref) async {
return await App.setUp();
});
final prefBoxProvider = FutureProvider<Box<Preferences>>((ref) async {
final HiveInterface hive = await ref.read(hiveProvider.future);
return hive.openBox<Preferences>("preferences");
});
class PreferencesNotifier extends StateNotifier<AsyncValue<Preferences>> {
late Box<Preferences> prefBox;
PreferencesNotifier(Future<Box<Preferences>> prefBoxFuture): super(const AsyncValue.loading()) {
prefBoxFuture.then((value) {
prefBox = value;
_loadCurrentPreferences();
});
}
void _loadCurrentPreferences() {
Preferences pref = prefBox.get(0) ?? Preferences();
state = AsyncValue.data(pref);
}
Future<void> save(Preferences prefs) async {
await prefBox.put(0, prefs);
state = AsyncValue.data(prefs);
}
Preferences? get preferences {
return state.when(data: (value) => value,
error: (_, __) => null,
loading: () => null);
}
}
final preferencesProvider = StateNotifierProvider<PreferencesNotifier, AsyncValue<Preferences>>((ref) {
return PreferencesNotifier(ref.read(prefBoxProvider.future));
});
And the following is the test case, I'm mocking the Hive box provider (prefBoxProvider):
class Listener extends Mock {
void call(dynamic previous, dynamic value);
}
Future<Box<Preferences>> prefBoxTesting() async {
final hive = await App.setUp();
Box<Preferences> box = await hive.openBox<Preferences>("testing_preferences");
await box.clear();
return box;
}
void main() {
test('Preferences value changes', () async {
final container = ProviderContainer(overrides: [
prefBoxProvider.overrideWithValue(AsyncValue.data(await prefBoxTesting()))
],);
addTearDown(() {
container.dispose();
Hive.deleteBoxFromDisk("testing_preferences");
});
final listener = Listener();
container.listen<AsyncValue<Preferences>>(
preferencesProvider,
listener,
fireImmediately: true,
);
verify(listener(null, const TypeMatcher<AsyncLoading>())).called(1);
verifyNoMoreInteractions(listener);
// Next line waits until we have a value for preferences attribute
await container.read(preferencesProvider.notifier).stream.first;
verify(listener(const TypeMatcher<AsyncLoading>(), const TypeMatcher<AsyncData>())).called(1);
Preferences preferences = Preferences.from(container.read(preferencesProvider.notifier).preferences!);
preferences.currentListName = 'Lista1';
await container.read(preferencesProvider.notifier).save(preferences);
verify(listener(const TypeMatcher<AsyncData>(), const TypeMatcher<AsyncData>())).called(1);
verifyNoMoreInteractions(listener);
final name = container.read(preferencesProvider.notifier).preferences!.currentListName;
expect(name, equals('Lista1'));
});
}
I've used as reference the official docs about testing Riverpod and the GitHub issue related with AsyncValues
Well, I found some problems to verify that the listener is called with the proper values, I used the TypeMatcher just to verify that the state instance has got the proper type and I check ("manually") the value of the wrapped object's attribute if It's the expected one. Is there a better way to achieve this ?
Finally, I didn't find too many examples with StateNotifier and AsyncValue as state type, Is there a better approach to implement providers that are initialized with deferred data ?
I didn't like too much my original approach so I created my own Matcher to compare wrapped values in AsyncValue instances:
class IsWrappedValueEquals extends Matcher {
final dynamic value;
IsWrappedValueEquals(this.value);
#override
bool matches(covariant AsyncValue actual, Map<dynamic, dynamic> matchState) =>
equals(actual.value).matches(value, matchState);
#override
Description describe(Description description) => description.add('Is wrapped value equals');
}
In the test, the final part is a bit different:
Preferences preferences = Preferences.from(container.read(preferencesProvider.notifier).preferences!);
preferences.currentListName = 'Lista1';
await container.read(preferencesProvider.notifier).save(preferences);
// the following line is the new one
verify(listener(IsWrappedValueEquals(Preferences()), IsWrappedValueEquals(preferences))).called(1);
verifyNoMoreInteractions(listener);
}
I prefer my custom Matcher to the original code, but I feel that there are too many custom code to test something, apparently, common.
If anyone can tell me a better solution for this case, It'd be great.

How to unit test that my class listen to a stream and responds correctly in Dart

Consider the following class:
enum LoginState { loggedOut, loggedIn }
class StreamListener {
final FirebaseAuth _auth;
LoginState _state = LoginState.loggedOut;
LoginState get state => _state;
StreamListener({required FirebaseAuth auth}) : _auth = auth {
_auth.userChanges().listen((user) {
if (user != null) {
_state = LoginState.loggedIn;
} else {
_state = LoginState.loggedOut;
}
});
}
}
I would like to test that when a user login the state changes from loggedOut to loggedIn, see the following test code:
class FakeUser extends Fake implements User {}
#GenerateMocks([FirebaseAuth])
void main() {
StreamController<User?> controller = StreamController<User?>();
final User value = FakeUser();
setUp(() {
controller = StreamController.broadcast();
});
tearDown(() {
controller.close();
});
test('Stream listen test', () {
final MockFirebaseAuth mockAuth = MockFirebaseAuth();
when(mockAuth.userChanges()).thenAnswer((_) => controller.stream);
StreamListener subject = StreamListener(auth: mockAuth);
controller.add(value);
expect(subject.state, LoginState.loggedIn);
});
}
However, due to the async behaviour the login state is still loggedOut. How could I test this properly?
I don't think that you can test that in a way that is strictly correct. Your StreamListener class promises to update state in response to Stream events, but it's asynchronous, you have no formal guarantee when those updates might happen, and you have no way to notify callers when those updates eventually do occur. You could solve that by modifying StreamListener to provide a broadcast Stream<LoginState> that is emitted whenever _state changes.
From a practical perspective of being good enough, there are a few things you could do:
Rely on Dart's event loop to invoke all of the Stream's listeners synchronously. In other words, after adding an event to your Stream, allow Dart to return the event loop so that the Stream's listeners can execute:
test('Stream listen test', () async {
...
StreamListener subject = StreamListener(auth: mockAuth);
controller.add(value);
await Future<void>.value();
expect(subject.state, LoginState.loggedIn);
Since you are using a broadcast Stream, you alternatively could rely on multiple Stream listeners firing in order of registration. (I don't see any formal documentation guaranteeing that ordering, but I think it is the sanest behavior.) Your test could register its own listener after your object has registered its listener, use expectAsync1 to verify that the test's listener is called, and have the test's listener verify the state of your object:
StreamListener subject = StreamListener(auth: mockAuth);
controller.stream.listen(expectAsync1((event) {
expect(event, value);
expect(subject.state, LoginState.loggedIn);
}));
controller.add(value);
Or combine the approaches:
test('Stream listen test', () async {
...
var eventReceived = Completer<void>();
StreamListener subject = StreamListener(auth: mockAuth);
controller.stream.listen((_) => eventReceived.complete());
controller.add(value);
await eventReceived.future;
expect(subject.state, LoginState.loggedIn);

AWS Amplify, how to check if user is logged in?

I've been using the aws-amplify library with ionic and was wondering how I would check if a user is logged in? I'm coming from a firebase background so this is quite different. This is so that I can grant access to certain pages based on the user's log in status. In my auth provider I import Amplify {Auth}. I can see that it's possible to get several pieces of data but I'm not sure what to use. There's currentUserPoolUser, getCurrentUser(), getSyncedUser(), currentAuthenticatedUser, currentSession, getCurrentUser(), userSession, currentUserCredentials, currentCredentials and currentUserInfo. I can't seem to find any documentation on any of this either. Everything I've read and watched covers up until the user signs in... Is this all supposed to be done on the client? Thanks.
I'm using the ionViewCanEnter() function in every page to allow/deny access. The return value of this function determines if the page can be loaded or not (and it is executed before running the costructor). Inside this function you have to implement you logic.
In my case, using Amplify, I'm doing this:
async function ionViewCanEnter() {
try {
await Auth.currentAuthenticatedUser();
return true;
} catch {
return false;
}
}
Since amplify currentAuthenticatedUser() return a promise I use async await to wait for the response to know if the user is logged in or not.
Hey I think for now you can only use Auth.currentUserInfo(); to detect whether logged in or not. It will return undefined if you are not logged in or an object if you are.
This can be achieved using the fetchAuthSession() method of Auth.
final CognitoAuthSession res = await Amplify.Auth.fetchAuthSession();
if (res.isSignedIn) {
// do your thang
}
if you are using angular with ionic then you can do somthing like this in your authenticator service
import {AmplifyService} from 'aws-amplify-angular';
...
constructor(private amplifyService:AmplifyService)
{
this.amplifyService.authStateChange$.subscribe(auth => {
switch (auth.state) {
case 'signedIn':
this.signedIn = true;
case 'signedOut':
this.signedIn = false;
break;
default:
this.signedIn = false;
}
}
}
then you can use this.signedIn in your router with canActivate guard.
Angular router guard: https://angular.io/guide/router#preventing-unauthorized-access
You can make it a custom hook by listening to the hub (ionViewCanEnter from the above answers is for bootup of the app):
Hook tsx:
import {useState, useEffect} from 'react';
import {Hub, Auth} from 'aws-amplify';
export default function AuthenticatedStatus(): Boolean {
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
async function ionViewCanEnter() {
console.log('hey');
try {
const authenticatedUser = await Auth.currentAuthenticatedUser();
if (authenticatedUser !== undefined) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
}
} catch {
setIsAuthenticated(false);
}
}
useEffect(() => {
ionViewCanEnter();
});
useEffect(() => {
const listener = data => {
switch (data.payload.event) {
case 'signIn' || 'autoSignIn' || 'tokenRefresh':
console.log('is authenticated');
setIsAuthenticated(true);
break;
case 'signOut' || 'signIn_failure' || 'tokenRefresh_failure' || 'autoSignIn_failure':
console.log('is not authenticated');
setIsAuthenticated(false);
break;
}
};
Hub.listen('auth', listener);
});
return isAuthenticated;
}
how to use:
const isAuthenticated = AuthenticatedStatus();
An example that's worked with me, careful for flow control, both
event-loop style and async/await style:
import { Auth } from "aws-amplify";
...
exampleIsLoggedIn() {
const notLoggedInStringThrown = "The user is not authenticated";
Auth.currentAuthenticatedUser().then(
// eslint-disable-next-line #typescript-eslint/no-unused-vars
(_currentAuthenticatedUser) => {
this.$log.debug("Yes, user is logged in.");
},
(error) => {
if (error === notLoggedInStringThrown) {
this.$log.debug("No, user is not yet logged in.");
} else {
this.$log.error(error);
}
}
);
},
async exampleIsLoggedInAsync() {
const notLoggedInStringThrown = "The user is not authenticated";
try {
/* currentAuthenticatedUser = */ await Auth.currentAuthenticatedUser();
this.$log.debug("Yes, user is logged in.");
} catch (error) {
if (error === notLoggedInStringThrown) {
this.$log.debug("No, user is not yet logged in.");
} else {
this.$log.error(error);
}
}
},
import { Auth } from 'aws-amplify';
Auth.currentAuthenticatedUser({
// Optional, By default is false. If set to true,
// this call will send a request to Cognito to get the latest user data
bypassCache: false
})
.then((user) => console.log(user))
.catch((err) => console.log(err));
This method can be used to check if a user is logged in when the page is loaded. It will throw an error if there is no user logged in. This method should be called after the Auth module is configured or the user is logged in. To ensure that you can listen on the auth events configured or signIn.
Source: https://docs.amplify.aws/lib/auth/manageusers/q/platform/js/#retrieve-current-authenticated-user

ionic media plugin - pass onStatusUpdate to mediaObject?

I'm trying to use ionic native's media plugin like this:
record(){
...
return this.media.create(src,onStatusUpdate).then((mediaObj) => {
mediaObj.startRecord();
return mediaObj;
});
}
And I use the mediaObj returned from it elsewhere, but I also need to know the status of the mediaObj; this apparently comes from passing a second argument, a callback function, to the media.create() function. However, I don't know how to get the mediaObj's status in there. I know the following would work for just telling me the status, but I actually need to access it.
const onStatusUpdate = (status) => console.log(status);
So, the question is, is there a way to simply access the mediaObj's status?
The MediaPlugin status update notification is all you get so set a class property with the value you get when the status change.
To manage the MediaObject I set a property to the value obtained when the promise is resolved.
import { ApplicationRef } from '#angular/core';
...
...
export class PlayerPage {
track:any;
file:MediaObject = undefined;
position:any = undefined;
status:any = 0;
constructor(public ref ApplicationRef, public navCtrl: NavController, private navParams: NavParams, public AppstateProvider: Appstate, private media: MediaPlugin) {
this.track = navParams.get('track');
media.create('http://.../...mp3',(status)=>{
this.status = status;
this.ref.tick();
}).then((file: MediaObject) => {
this.file = file;
});
}
play() {
this.file.play();
}
The this.ref.tick(); is necesarry because Angular does not detect this property update - I tried publishing and subscribing Angular still did not detect the property update.