Testing angular services in rails - testing

I'm trying to test my services with jasmine and I keep gettin and "Unknown provider: AuthServiceProvider <- AuthService in angular/angular.js on line 2683"
my service is defined:
app.factory( 'AuthService', ["$resource", "$rootScope", "apiPrefix", function($resource, $rootScope, apiPrefix) {
auth_resource = $resource(apiPrefix + "/session", {}, {
logout: {method:'GET'}
});
var currentUser;
return {
login: function(email, password, success, failure) {
auth_resource.save({}, {
email: email,
password: password
}, function(response){
currentUser = response
success()
}, function(response){
failure()
});
},
logout: function(success, failure) {
auth_resource.logout(
function(response){
currentUser = undefined
}, function(){
$scope.alerts.push({type: "success", msg: "Logged out" })
}, function(){
$scope.alerts.push({type: "error", msg: "Sorry, something went wrong" })
}
)
},
isLoggedIn: function(){ return currentUser !== undefined},
currentUser: function() { return currentUser; }
};
}]);
and my test:
describe("AuthService", function(){
var httpBackend;
beforeEach(inject(function($httpBackend, AuthService){
module('app');
httpBackend = $httpBackend;
AService = AuthService;
}));
it("should login the user", function(){
// test here
});
});
my jasmine config file is:
// This pulls in all your specs from the javascripts directory into Jasmine:
// spec/javascripts/*_spec.js.coffee
// spec/javascripts/*_spec.js
// spec/javascripts/*_spec.js.erb
//= require application
//= require_tree ./
This seems to be configured properly because I can test my controllers fine so I'm not sure why it doesn't recognize my services.

You can use $injector to get the service and then inject it to the actual test like this
describe("AuthService", function () {
var httpBackend, AService, apiPrefix;
beforeEach(module('app'));
beforeEach(function () {
angular.mock.inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
apiPrefix = angular.mock.module('apiPrefix'); // I assume you have apiPrefix module defined somewhere in your code.
AService = $injector.get('AuthService', {apiPrefix: apiPrefix});
})
});
it("should login the user", inject(function (AService) {
// test here
}));
});
I assume you have apiPrefix module defined somewhere in your code.

Related

React Blitz.js 3rd party auth failing with passport-azure-ad

I'm attempting to swap the default auth scheme in Blitz.js with a passport-azure-ad scheme, using the OIDCStrategy. I'm getting an error that I'm not sure about and would appreciate any help! I've created a new file under src/pages/auth/openid.tsx and into inserted the following code:
import { passportAuth } from "#blitzjs/auth"
import { api } from "src/blitz-server"
import { OIDCStrategy } from "passport-azure-ad"
const users: Array<{ oid: string }> = []
var findByOid = function (oid, fn) {
console.log("failing")
for (var i = 0, len = users.length; i < len; i++) {
const user = users[i]
console.log("we are using user: ", user)
if (user && user.oid === oid) {
return fn(null, user)
}
}
return fn(null, null)
}
export default api(
passportAuth({
successRedirectUrl: "/",
errorRedirectUrl: "/",
strategies: [
{
strategy: new OIDCStrategy(
{
identityMetadata:
"https://login.microsoftonline.com/<tenant-nam>.onmicrosoft.com/v2.0/.well-known/openid-configuration",
clientID: <client-id>,
responseType: "code id_token",
responseMode: "form_post",
redirectUrl: "http://localhost:3000/auth/openid/callback",
allowHttpForRedirectUrl: true,
clientSecret: "<client-secret>",
validateIssuer: false,
passReqToCallback: true,
scope: ["profile", "offline_access", "https://graph.microsoft.com/mail.read"],
loggingLevel: "info",
nonceMaxAmount: 5,
useCookieInsteadOfSession: false,
cookieEncryptionKeys: [
{ key: "12345678901234567890123456789012", iv: "123456789012" },
{ key: "abcdefghijklmnopqrstuvwxyzabcdef", iv: "abcdefghijkl" },
],
},
function (iss, sub, profile, accessToken, refreshToken, done) {
if (!profile.oid) {
return done(new Error("No oid found"), null)
}
// asynchronous verification, for effect...
process.nextTick(function () {
findByOid(profile.oid, function (err, user) {
if (err) {
return done(err)
}
if (!user) {
// "Auto-registration"
users.push(profile)
return done(null, profile)
}
return done(null, user)
})
})
}
),
},
],
})
)
I believe the configuration is good because I can run the example from passport-azure-ad from the github examples. The only change I make is that I set redirectUrl: "http://localhost:3000/auth/openid/callback", instead of redirectUrl: ".../return", per the blitz.js third party auth documentation. The tenantname, client_id, client_secret are redacted but I do set them to the correct values. I have also verified that the app registration is correctly set with the correct redirect uri.
I run blitz dev and when I go to the http://localhost:3000/auth/openid route I get the following error.
Here is the console output that is produced:
As you can see there is a Module not found: Can't resolve './src/build', this error only occurs if I go to the auth/openid page but the app is able to load.

How can I set Next-Auth callback url? and next-auth session return null

I want to set login, logout callback url.
So, I set the callback url like this.
//signIn
const signInResult = await signIn("credentials", {
message,
signature,
redirect: false,
callbackUrl: `${env.nextauth_url}`,
});
//signOut
signOut({ callbackUrl: `${env.nextauth_url}`, redirect: false });
But, When I log in, I look at the network tab.
api/auth/providers, api/auth/callback/credentials? reply with
callbackUrl(url) localhost:3000
It's api/auth/callback/credentials? reply.
It's api/auth/providers reply
and api/auth/session reply empty object.
When I run on http://localhost:3000, everything was perfect.
But, After deploy, the login is not working properly.
How can I fix the error?
I added [...next-auth] code.
import CredentialsProvider from "next-auth/providers/credentials";
import NextAuth from "next-auth";
import Moralis from "moralis";
import env from "env.json";
export default NextAuth({
providers: [
CredentialsProvider({
name: "MoralisAuth",
credentials: {
message: {
label: "Message",
type: "text",
placeholder: "0x0",
},
signature: {
label: "Signature",
type: "text",
placeholder: "0x0",
},
},
async authorize(credentials: any): Promise<any> {
try {
const { message, signature } = credentials;
await Moralis.start({
apiKey: env.moralis_api_key,
});
const { address, profileId } = (
await Moralis.Auth.verify({ message, signature, network: "evm" })
).raw;
if (address && profileId) {
const user = { address, profileId, signature };
if (user) {
return user;
}
}
} catch (error) {
console.error(error);
return null;
}
},
}),
],
pages: {
signIn: "/",
signOut: "/",
},
session: {
maxAge: 3 * 24 * 60 * 60,
},
callbacks: {
async jwt({ token, user }) {
user && (token.user = user);
return token;
},
async session({ session, token }: any) {
session.user = token.user;
return session;
},
async redirect({ url, baseUrl }) {
// Allows relative callback URLs
if (url.startsWith("/")) return `${baseUrl}${url}`;
// Allows callback URLs on the same origin
else if (new URL(url).origin === baseUrl) return url;
return baseUrl;
},
},
secret: env.nextauth_secret,
});

Testing authentication with Auth0 in a full stack application with Cypress

I’m working on a full-stack NestJS application, integrating with Auth0 using the express-openid-connect library. I’m using Cypress for e2e tests, and I’m trying to find a way of testing my login using Cypress.
I found this article - https://auth0.com/blog/end-to-end-testing-with-cypress-and-auth0/, but it seems to be very much tied to a React application. I’m calling the /oauth/token API endpoint, and I get a response, but I’m unsure how to build out my callback URL to log me in to the application. Here’s what I have so far:
Cypress.Commands.add('login', () => {
cy.session('logged in user', () => {
const options = {
method: 'POST',
url: `${Cypress.env('OAUTH_DOMAIN')}/oauth/token`,
body: {
grant_type: 'password',
username: Cypress.env('AUTH_USERNAME'),
password: Cypress.env('AUTH_PASSWORD'),
scope: 'openid profile email',
audience: `${Cypress.env('OAUTH_DOMAIN')}/api/v2/`,
client_id: Cypress.env('OAUTH_CLIENT_ID'),
client_secret: Cypress.env('OAUTH_CLIENT_SECRET'),
},
};
cy.request(options).then((response) => {
// What do I do here?
});
});
});
Any pointers would be gratefully recieved!
I ended up sorting this out by using Puppeteer to handle my login, stopping at the point of redirection to the callback URL and returning the cookies and callback URL to Cypress, as detailed in this article:
https://sandrino.dev/blog/writing-cypress-e2e-tests-with-auth0
Things have changed a bit since then, and with the introduction of Cypress's experimentalSessionSupport it's a bit simpler. I ended up whittling the solution down to having the following in my Cypress setup:
// cypress/plugins/auth0.js
const puppeteer = require('puppeteer');
const preventApplicationRedirect = function (callbackUrl) {
return (request) => {
const url = request.url();
if (request.isNavigationRequest() && url.indexOf(callbackUrl) === 0)
request.respond({ body: url, status: 200 });
else request.continue();
};
};
const writeUsername = async function writeUsername({ page, options } = {}) {
await page.waitForSelector('#username');
await page.type('#username', options.username);
};
const writePassword = async function writeUsername({ page, options } = {}) {
await page.waitForSelector('#password', { visible: true });
await page.type('#password', options.password);
};
const clickLogin = async function ({ page } = {}) {
await page.waitForSelector('button[type="submit"]', {
visible: true,
timeout: 5000,
});
const [response] = await Promise.all([
page.waitForNavigation({ waitUntil: 'networkidle2' }),
page.click('button[type="submit"]'),
]);
return response;
};
exports.Login = async function (options = {}) {
const browser = await puppeteer.launch({
headless: options.headless,
args: options.args || ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
try {
await page.setViewport({ width: 1280, height: 800 });
await page.setRequestInterception(true);
page.on('request', preventApplicationRedirect(options.callbackUrl));
await page.goto(options.loginUrl);
await writeUsername({ page, options });
await writePassword({ page, options });
const response = await clickLogin({ page, options });
if (response.status() >= 400) {
throw new Error(
`'Login with user ${
options.username
} failed, error ${response.status()}`,
);
}
const url = response.url();
if (url.indexOf(options.callbackUrl) !== 0) {
throw new Error(`User was redirected to unexpected location: ${url}`);
}
const { cookies } = await page._client.send('Network.getAllCookies', {});
return {
callbackUrl: url,
cookies,
};
} finally {
await page.close();
await browser.close();
}
};
// cypress/plugins/index.js
const auth0 = require('./auth0');
module.exports = (on, config) => {
require('dotenv').config({ path: '.env.test' });
config.env.AUTH0_DOMAIN = process.env.AUTH0_DOMAIN;
config.env.AUTH_USERNAME = process.env.AUTH_USERNAME;
config.env.AUTH_PASSWORD = process.env.AUTH_PASSWORD;
on('task', {
LoginPuppeteer(options) {
return auth0.Login(options);
},
});
return config;
};
// cypress/support/commands.js
const { getUnixTime } = require('date-fns');
/*
* Create the cookie expiration.
*/
function getFutureTime(minutesInFuture) {
const time = new Date(new Date().getTime() + minutesInFuture * 60000);
return getUnixTime(time);
}
/**
* Create a cookie object.
* #param {*} cookie
*/
function createCookie(cookie) {
return {
name: cookie.name,
value: cookie.value,
options: {
domain: `${cookie.domain.trimLeft('.')}`,
expiry: getFutureTime(15),
httpOnly: cookie.httpOnly,
path: cookie.path,
sameSite: cookie.sameSite,
secure: cookie.secure,
session: cookie.session,
},
};
}
/**
* Login via puppeteer and return the redirect url and cookies.
*/
function login() {
return cy.task('LoginPuppeteer', {
username: Cypress.env('AUTH_USERNAME'),
password: Cypress.env('AUTH_PASSWORD'),
loginUrl: 'http://localhost:3000/login',
callbackUrl: 'http://localhost:3000/callback',
});
}
/**
* Login with Auth0.
*/
Cypress.Commands.add('loginAuth0', () => {
cy.session('logged in user', () => {
login().then(({ cookies, callbackUrl }) => {
console.log(cookies);
cookies
.map(createCookie)
.forEach((c) => cy.setCookie(c.name, c.value, c.options));
cy.visit(callbackUrl);
});
});
});
You can then use cy.loginAuth0() in your app to login with a real Auth0 instance. Make sure you have "experimentalSessionSupport": true in your cypress.json. That way you'll only have to perform this (admittedly long winded) task only once in your test suite!

Catch error server response with #nuxtjs/auth

I'm trying to catch the error response for #nuxtjs/auth but it doesn't seem to return anything but undefined.
It refuses to login if I include the user so I want to know why it's returning undefined.
CONFIG:
auth: {
strategies: {
local: {
endpoints: {
login: {
url: 'http://127.0.0.1:80/api/login',
method: 'post',
propertyName: 'token'
},
logout: false,
user: {
url: 'http://127.0.0.1:80/api/me',
method: 'get',
propertyName: undefined
}
},
tokenRequired: true,
tokenType: 'bearer',
}
},
plugins: [
'#/plugins/auth.js'
]
},
PLUGIN:
export default function ({ app }) {
app.$auth.onError((error, name, endpoint) => {
console.error(name, error)
});
}
VIEW FUNCTION:
- both handleSuccess and handleFailure returns undefined.
login() {
this.toggleProcessing(0);
let payload = {
username: 'admin',
password: 'admin123'
}
let handleSuccess = response => {
console.log(response);
this.toggleProcessing(0);
}
let handleFailure = error => {
console.log(error);
this.toggleProcessing(0);
}
this.$auth.loginWith('local', { data: payload }).then(handleSuccess).catch(handleFailure);
},
You can use e.response
async login() {
try {
const login = {
username: this.username,
password: this.password
}
let response = await this.$auth.loginWith('local', { data: login })
console.log('response', response)
} catch (e) {
console.log('Error Response', e.response)
}
}
I fell into the same problem and after spending some time i found out a very good way to catch the response. The solution is to use the axios interceptor. Just replace your plugin file code with the following
export default function ({$axios, $auth}){
$axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});
}
I'm not sure initially what might be wrong here because I can't see the complete nuxt.config.js and your full component but here are a few things to check:
#nuxtjs/axios is installed
Both axios and auth modules are registered in the modules section of nuxt.config.js:
modules: [
'#nuxtjs/axios',
'#nuxtjs/auth'
]
Also, ensure the middleware property for auth is set in the component/page component.
Ensure you're following the documentation on this page: https://auth.nuxtjs.org/getting-starterd/setup
Ive been using try -> this.$auth.loginWith to catch error server response with #nuxtjs/auth.
login() {
const data = { form };
try {
this.$auth
.loginWith("local", { data: data })
.then(api => {
// response
this.response.success = "Succes";
})
.catch(errors => {
this.response.error = "Wrong username/password";
});
} catch (e) {
this.response.error = e.message;
}
},
Specify the token field in the nuxt.config
strategies: {
local: {
endpoints: {
login: { // loginWith
url: "auth/login",
method: "post",
propertyName: "data.token" // token field
},
user: { // get user data
url: "auth/user",
method: "get",
propertyName: "data.user"
},
}
}
},
modules: ["#nuxtjs/axios", "#nuxtjs/auth"],

Invalidate session with custom authenticator

Using ember-cli 0.1.2 and ember-cli-simple-auth 0.7.0, I need to invalidate the session both on client and server. As explained here I need to do something similar to the authenticate method making an ajax request to the server and ensuring its success before emptying the session:
import Ember from 'ember';
import Base from "simple-auth/authenticators/base";
var CustomAuthenticator = Base.extend({
tokenEndpoint: 'http://127.0.0.1:3000/api/v1/auth/login',
restore: function(data) {
},
authenticate: function(credentials) {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'POST',
data: JSON.stringify({ email: credentials.identification, password: credentials.password }),
contentType: 'application/json'
}).then(function(response) {
Ember.run(function() {
resolve({ token: response.token });
});
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
},
invalidate: function() {
var _this = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.$.ajax({
url: _this.tokenEndpoint,
type: 'DELETE'
}).then(function(response) {
resolve();
}, function(xhr, status, error) {
var response = JSON.parse(xhr.responseText);
Ember.run(function() {
reject(response.error);
});
});
});
}
// invalidate: function() {
// var _this = this;
// return new Ember.RSVP.Promise(function(resolve) {
// Ember.$.ajax({ url: _this.tokenEndpoint, type: 'DELETE' }).always(function() {
// resolve();
// });
// });
// }
});
export default {
name : 'authentication',
before : 'simple-auth',
initialize : function(container) {
container.register('authenticator:custom', CustomAuthenticator);
}
};
My logout API endpoint need the token (in the headers). How do I pass it? I read this but my authorizer seems ignoring it and I got a 401:
import Ember from 'ember';
import Base from 'simple-auth/authorizers/base';
var CustomAuthorizer = Base.extend({
authorize: function(jqXHR, requestOptions){
Ember.debug("AUTHORIZING!");
}
});
export default {
name : 'authorization',
before : 'simple-auth',
initialize : function(container) {
container.register('authorizer:custom', CustomAuthorizer);
}
};
My environment.js:
/* jshint node: true */
module.exports = function(environment) {
var ENV = {
modulePrefix: 'wishhhh',
environment: environment,
baseURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. 'with-controller': true
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
}
};
// TODO: disabled because of https://github.com/stefanpenner/ember-cli/issues/2174
ENV.contentSecurityPolicyHeader = 'Disabled-Content-Security-Policy'
ENV['simple-auth'] = {
authorizer: 'authorizer:custom',
// crossOriginWhitelist: ['http://localhost:3000']
crossOriginWhitelist: ['*']
}
if (environment === 'development') {
// ENV.APP.LOG_RESOLVER = true;
ENV.APP.LOG_ACTIVE_GENERATION = true;
// ENV.APP.LOG_TRANSITIONS = true;
// ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
ENV.APP.LOG_VIEW_LOOKUPS = true;
}
if (environment === 'test') {
// Testem prefers this...
ENV.baseURL = '/';
ENV.locationType = 'auto';
// keep test console output quieter
ENV.APP.LOG_ACTIVE_GENERATION = false;
ENV.APP.LOG_VIEW_LOOKUPS = false;
ENV.APP.rootElement = '#ember-testing';
}
if (environment === 'production') {
}
return ENV;
};
The following is the Ember inspector output when, eventually, I try to logout:
Did you actually configure Ember Simple Auth to use your custom authorizer? In that case it should authorize the session invalidation request automatically.
Alternatively you could add the token in the authenticator's invalidate method which gets passed the session's contents.
Thanks to marcoow, I found out that it was actually a problem with every request not only the logout one. My authorizer never got called. Problem was environment setup of crossOriginWhitelist which, in order to work with my dev API, I had to set to ['http://127.0.0.1:3000']. Neither ['http://localhost:3000'] nor [*] worked.