Skip to content

Commit c2b1d6d

Browse files
committed
initial commit
0 parents  commit c2b1d6d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+6193
-0
lines changed

.gitignore

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.idea/
2+
bower_components/
3+
node_modules/
4+
tmp/
5+
.tmp/
6+
.sass-cache
7+
.tmp/*
8+
dist/
9+
assets/
10+
.DS_Store
11+
npm-debug.log
12+
.env
13+
.env.*
14+
tests/sqlite.db
15+
*.log
16+
*.iml

.sequelizerc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const path = require('path');
2+
3+
module.exports = {
4+
'config': path.resolve('dist/config', 'config.js')
5+
}

README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Express Typescript Seed
2+
[Node.js](https://nodejs.org) with [Express 4](http://expressjs.com/4x) written in [Typescript](https://www.typescriptlang.org/)
3+
4+
[PostgreSQL](https://www.postgresql.org) database under [Sequelize](http://docs.sequelizejs.com/) ORM
5+
6+
OAuth2 with [Passport](http://passportjs.org/)
7+
8+
Roles based access with [Connect Roles](https://github.com/ForbesLindesay/connect-roles)
9+
10+
Message brokering with [RabbitMQ](https://www.rabbitmq.com/) for running background tasks like sending e-mails and uploading images to S3
11+
12+
Environment based configuration using [Dotenv](https://www.npmjs.com/package/dotenv)
13+
14+
Integration Testing with [SuperTest](https://github.com/visionmedia/supertest)
15+
16+
## Environment Setup
17+
This project uses the [Dotenv](https://www.npmjs.com/package/dotenv) library to load sensitive data such
18+
as database passwords and client secrets.
19+
20+
There is a `.env` file included at the root of this project as an example, but
21+
you should never check this file into your source control. Update the `.env` file with the pertinent information
22+
for your project and remove it from your source control system.
23+
24+
### RabbitMQ
25+
Install and run [RabbitMQ](https://www.rabbitmq.com/) with the default settings
26+
27+
### Database
28+
You will need a [PostgreSQL](https://www.postgresql.org) database running on localhost:5432
29+
30+
The setup of PostgreSQL is beyond the scope of this guide. Please reference the [Install Guides](https://wiki.postgresql.org/wiki/Detailed_installation_guides)
31+
for help installing PostgreSQL on your machine.
32+
33+
Once PostgreSQL is installed and running, create a new database called `seed`. Create a new user named `seed`. Make this user the owner of the newly created database.
34+
35+
Since the tables defined in the entities do not already exist, Sequelize will attempt to build them once you start the server.
36+
37+
## Running the app
38+
yarn install
39+
yarn run start
40+
You can also run the app in debug mode and attach a [Debugger](https://www.jetbrains.com/help/webstorm/run-debug-configuration-attach-to-node-js-chrome.html) in Webstorm
41+
42+
yarn run debug
43+
44+
Once the app is running and the tables are created, you can seed the database with the sequelize-cli.
45+
Install the sequelize-cli by running
46+
47+
yarn install --g sequelize-cli
48+
49+
then run
50+
51+
sequelize db:seed:all
52+
53+
### Running the tests
54+
yarn run test
55+
56+
#### Obtaining a Bearer Token
57+
In order to make requests to the API you will need to send an Authorization header with a bearer token attached, e.g.,
58+
59+
Authorization: Bearer {bearerToken}
60+
61+
To get a valid token you can hit http://localhost:3000/oauth/token with a body including your username, password and the grant_type of password, e.g.
62+
63+
curl -X POST \
64+
http://localhost:3000/oauth/token \
65+
-H 'content-type: application/x-www-form-urlencoded' \
66+
-d 'username=kckolz%40gmail.com&password=password&grant_type=password'
67+
68+
## Contact
69+
Kevin Kolz - kckolz@gmail.com
70+
71+
## License
72+
MIT

app.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* Create Server
3+
*/
4+
import { Server } from "./server";
5+
6+
Server.initializeApp().then(() => {
7+
console.log((" App is running at http://localhost:%d in %s mode"), Server.app.get("port"), Server.app.get("env"));
8+
});

auth/auth.ts

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/**
2+
* Module dependencies.
3+
*/
4+
import * as passport from "passport";
5+
import * as LocalStrategy from "passport-local";
6+
import * as ClientPasswordStrategy from "passport-oauth2-client-password";
7+
import * as BearerStrategy from "passport-http-bearer";
8+
import * as bcrypt from "bcrypt";
9+
import {BasicStrategy} from "passport-http";
10+
import {User} from "../models/entities/User";
11+
import {AccessToken} from "../models/entities/AccessToken";
12+
import {Client} from "../models/entities/Client";
13+
import {sign, verify} from 'jsonwebtoken';
14+
import {AuthError} from "../errors/AuthError";
15+
16+
const FacebookTokenStrategy = require('passport-facebook-token');
17+
18+
export class Auth {
19+
20+
static serializeUser() {
21+
passport.serializeUser(function (user: any, done) {
22+
done(null, user.id);
23+
});
24+
25+
passport.deserializeUser(function (id: number, done) {
26+
User.find<User>({where: {id}}).then(function (user: User) {
27+
done(null, user);
28+
});
29+
});
30+
}
31+
32+
/**
33+
* LocalStrategy
34+
*
35+
* This strategy is used to authenticate users based on a username and password.
36+
* Anytime a request is made to authorize an application, we must ensure that
37+
* a user is logged in before asking them to approve the request.
38+
*/
39+
static useLocalStrategy() {
40+
passport.use(new LocalStrategy( async(userName, password, done) => {
41+
let user = await User.findOne<User>({where: {email: userName}});
42+
if(user) {
43+
const authorized = await this.comparePasswords(password, user.password);
44+
if(authorized) {
45+
return done(null, user);
46+
} else {
47+
return done(null, false)
48+
}
49+
} else {
50+
return done("No user found", false);
51+
}
52+
}));
53+
}
54+
55+
static async comparePasswords(pass1: string | undefined, pass2: string | undefined): Promise<boolean> {
56+
if(pass1 && pass2) {
57+
return bcrypt.compare(pass1, pass2);
58+
} else {
59+
return false;
60+
}
61+
}
62+
63+
/**
64+
* BearerStrategy
65+
*
66+
* This strategy is used to authenticate users based on an access token (aka a
67+
* bearer token). The user must have previously authorized a client
68+
* application, which is issued an access token to make requests on behalf of
69+
* the authorizing user.
70+
*/
71+
static useBearerStrategy() {
72+
passport.use(new BearerStrategy((token, done) => {
73+
AccessToken.findOne<AccessToken>({where: {token: token}}).then(accessToken => {
74+
if (accessToken) {
75+
const jwtSecret: string | undefined = process.env.JWT_SECRET;
76+
if(jwtSecret) {
77+
verify(accessToken.token, jwtSecret, (err, decodedToken: any) => {
78+
if (decodedToken && accessToken.userId === decodedToken.id) {
79+
User.find({where: {id: accessToken.userId}}).then(user => {
80+
return done(null, user);
81+
}).catch(error => {
82+
return done(new AuthError(error.message), false);
83+
});
84+
} else {
85+
done(new AuthError(err.message), false);
86+
}
87+
});
88+
}
89+
} else {
90+
return done(new AuthError("Unauthorized"), false);
91+
}
92+
}).catch(function (error) {
93+
return done(new AuthError(error.message), false);
94+
});
95+
}));
96+
}
97+
98+
99+
/**
100+
* BasicStrategy & ClientPasswordStrategy
101+
*
102+
* These strategies are used to authenticate registered OAuth clients. They are
103+
* employed to protect the `token` endpoint, which consumers use to obtain
104+
* access tokens. The OAuth 2.0 specification suggests that clients use the
105+
* HTTP Basic scheme to authenticate. Use of the client password strategy
106+
* allows clients to send the same credentials in the request body (as opposed
107+
* to the `Authorization` header). While this approach is not recommended by
108+
* the specification, in practice it is quite common.
109+
*/
110+
static useBasicStrategy() {
111+
passport.use(new BasicStrategy(
112+
function (clientId, clientSecret, done) {
113+
Client.findOne({
114+
where: {clientId: clientId}
115+
}).then(function (client: any) {
116+
if (!client) return done(null, false);
117+
if (!bcrypt.compareSync(clientSecret, client.clientSecret)) return done(null, false);
118+
return done(null, client);
119+
}).catch(function (error) {
120+
return done(error);
121+
});
122+
}
123+
));
124+
125+
passport.use(new ClientPasswordStrategy(
126+
function (clientId, clientSecret, done) {
127+
Client.findOne({
128+
where: {clientId: clientId}
129+
}).then(function (client: any) {
130+
if (!client) return done(null, false);
131+
if (!bcrypt.compareSync(clientSecret, client.clientSecret)) return done(null, false);
132+
return done(null, client);
133+
}).catch(function (error) {
134+
return done(error);
135+
});
136+
}
137+
));
138+
}
139+
140+
static useFacebookTokenStrategy() {
141+
passport.use(new FacebookTokenStrategy({
142+
clientID: process.env.FACEBOOK_CLIENT_ID,
143+
clientSecret: process.env.FACEBOOK_CLIENT_SECRET
144+
}, (accessToken, refreshToken, profile, done) => {
145+
const parsedProfile = profile._json;
146+
User.findOne<User>({where: {email: parsedProfile.email}}).then(user => {
147+
if (user) {
148+
AccessToken.findOne<AccessToken>({where: {userId: user.id}}).then(accessToken => {
149+
if (accessToken) {
150+
const jwtSecret: string | undefined = process.env.JWT_SECRET;
151+
if(jwtSecret) {
152+
verify(accessToken.token, jwtSecret, (err, decodedToken: any) => {
153+
if (decodedToken && accessToken.userId === decodedToken.id) {
154+
return done(null, accessToken);
155+
} else {
156+
return done(new AuthError(err.message), false);
157+
}
158+
});
159+
} else {
160+
return done(new AuthError("JWT Secret undefined"), false);
161+
}
162+
} else {
163+
const jwtSecret: string | undefined = process.env.JWT_SECRET;
164+
if(jwtSecret) {
165+
sign(user, jwtSecret, {expiresIn: "10h"}, (error, token) => {
166+
AccessToken.create({
167+
token: token,
168+
userId: user.id
169+
}).then((accessToken: AccessToken) => {
170+
return done(null, accessToken);
171+
}).catch(error => {
172+
return done(error, false);
173+
});
174+
});
175+
} else {
176+
return done(new AuthError("JWT Secret undefined"), false);
177+
}
178+
179+
}
180+
});
181+
} else {
182+
return done("No account found with email: " + parsedProfile.email);
183+
}
184+
});
185+
}
186+
));
187+
}
188+
189+
public static getBearerMiddleware() {
190+
return passport.authenticate('bearer', {session: false, failWithError: true});
191+
}
192+
}
193+
194+
195+
196+
197+
198+
199+
200+

0 commit comments

Comments
 (0)