Skip to content

Commit

Permalink
V0.0.20/bitloops authentication (#19)
Browse files Browse the repository at this point in the history
* added readme

* Merge config of initialize and authenticate

* Update README

* Increased version to 0.0.20

* Added authenticationWithGoogle

Co-authored-by: Markos Girgis <markos@bitloops.com>
  • Loading branch information
danias and Varagos committed Jan 21, 2022
1 parent 7962205 commit 2213e66
Show file tree
Hide file tree
Showing 7 changed files with 163 additions and 78 deletions.
73 changes: 33 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ $ yarn add bitloops
### TypeScript Frontend usage Example

```ts
import Bitloops, { AuthTypes } from 'bitloops';
import { getAuth, onAuthStateChanged, onAuthStateChanged } from 'firebase/auth'; // If you are using Firebase
import Bitloops, { AuthTypes, BitloopsUser, getAuth } from 'bitloops';

// You will get this from your Console in your Workflow information
const bitloopsConfig = {
Expand All @@ -33,52 +32,46 @@ const bitloopsConfig = {
environmentId: "3c42a5ef-fe21-4b50-8128-8596ea47da93",
workspaceId: "4f7a0fc5-fe2f-450a-b246-11a0873e91f0",
messagingSenderId: "742387243782",
auth: {
authenticationType: AuthTypes.User,
providerId: 'myProviderId', // You create this in the Bitloops Console
clientId: 'myWebAppId', // You create this in the Bitloops Console
}
}

bitloops.initialize(bitloopsConfig);

const auth = getAuth();
const refreshTokenFunction =
(): Promise<string | null> => new Promise<string | null>((resolve, reject) => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
unsubscribe();
if (user) {
getIdToken(user, true).then((idToken: string) => {
resolve(idToken);
}, (error) => {
reject(error);
});
} else {
resolve(null);
}
});
});
onAuthStateChanged(auth, (user) => {
const bitloops = Bitloops.initialize(bitloopsConfig);

bitloops.auth.authenticateWithUsername('username', 'email', 'password');
bitloops.auth.authenticateWithEmail('email', 'password');
bitloops.auth.authenticateWithEmailLink('email');
bitloops.auth.authenticateWithEmailLinkVerification('link');
bitloops.auth.forgotPassword('email', 'username');
bitloops.auth.forgotPassword('email');
bitloops.auth.forgotPasswordLink('link');
bitloops.auth.forgotPasswordLink('link', 'new-password');

bitloops.auth.authenticateWithGoogle();
bitloops.auth.registerWithGoogle();
bitloops.auth.addGoogle();
bitloops.auth.authenticateWithGitHub();
bitloops.auth.registerWithGitHub();
bitloops.auth.addGitHub();
bitloops.auth.authenticateWithTwitter();
bitloops.auth.registerWithTwitter();
bitloops.auth.addTwitter();

bitloops.auth.getUser();
bitloops.auth.clear();

bitloops.auth.onAuthStateChange((user: BitloopsUser) => {
if (user) {
// If you are using Firebase authentication you need to pass
// the user auth data as context for your web requests
bitloops.authenticate({
authenticationType: AuthTypes.FirebaseUser,
providerId: 'myProviderId', // You set this in the Bitloops Console
user,
refreshTokenFunction, // used to refresh the token when it expires
});
// Do stuff when authenticated
} else {
// User is signed out
bitloops.signOut();
// Do stuff if authentication is cleared
}
});

...
// If you want to pass a username/password combo you should first initialize
// and then authenticate using a username / password combo over https

// await bitloops.initialize(bitloopsConfig);
// bitloops.authenticate({
// provider: AuthProviders.BITLOOPS_USER_PASS,
// username,
// password,
// });

const userInfo = await bitloops.request('db7a654a-1e2c-4f9c-b2d0-8ff2e2d6cbfe', '70e3084f-9056-4905-ac45-a5b65c926b1b');
const productInfo = await bitloops.request('64f264ad-76b1-4ba1-975c-c7b9795e55ce', '70e3084f-9056-4905-ac45-a5b65c926b1b', { productId: '7829' });
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "bitloops",
"version": "0.0.19",
"version": "0.0.20",
"description": "NodeJS library for the Bitloops",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down Expand Up @@ -32,12 +32,13 @@
},
"homepage": "https://bitloops.com",
"dependencies": {
"axios": "^0.24.0",
"eventsource": "^1.1.0"
"axios": "^0.25.0",
"eventsource": "^1.1.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/eventsource": "^1.1.8",
"@types/node": "^17.0.8",
"@types/node": "^17.0.10",
"nodemon": "^2.0.15",
"ts-node": "^10.3.0"
}
Expand Down
59 changes: 59 additions & 0 deletions src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { v4 as uuid } from 'uuid';
import { AuthTypes, BitloopsConfig, IBitloopsAuthenticationOptions } from './index';
import { AuthenticationOptionsType } from '.';
import Bitloops from './index';
import { BitloopsUser } from './definitions';

class auth {
private static authOptions?: AuthenticationOptionsType;
private static bitloopsConfig: BitloopsConfig;
private static bitloops: Bitloops;
private static authChangeCallback: ((user: BitloopsUser) => void) | null;

static setBitloops(bitloops: Bitloops) {
auth.bitloops = bitloops;
}

static setAuthOptions(options?: AuthenticationOptionsType) {
auth.authOptions = options;
}

static setBitloopsConfig(config: BitloopsConfig) {
auth.bitloopsConfig = config;
}

static authenticateWithGoogle() {
if (auth.bitloops.authOptions?.authenticationType !== AuthTypes.User) throw new Error('Auth type must be User');
const sessionUuid = uuid();
const url = `${auth.bitloops.config.ssl === false ? 'http' : 'https'}://${
auth.bitloops.config.server
}/bitloops/auth/google?client_id=${auth.bitloops.authOptions.clientId}&provider_id=${auth.bitloops.authOptions.providerId}&session_uuid=${sessionUuid}`;
if (typeof window !== 'undefined') {
window.open(url, '_blank');
auth.bitloops.subscribe(`workflow-events.auth:${auth.bitloops.authOptions.providerId}:${sessionUuid}`, (user: BitloopsUser) => {
if (auth.authChangeCallback) auth.authChangeCallback(user);
})
}
}

static clear() {
const clearedAuthOptions = auth.bitloops.authOptions as IBitloopsAuthenticationOptions;
// TODO communicate logout to REST
clearedAuthOptions.token = null;
if (auth.authChangeCallback) auth.authChangeCallback(null);
auth.bitloops.authOptions = clearedAuthOptions;
}

registerWithGoogle() {}
getUser() {}

static onAuthStateChange(authChangeCallback: (user: BitloopsUser) => void) {
if (auth.bitloops.authOptions && auth.bitloops.authOptions.authenticationType === AuthTypes.User) {
auth.authChangeCallback = authChangeCallback;
} else {

}
}
}

export default auth;
13 changes: 13 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type BitloopsUser = {
token: string;
} | null;

export enum AuthTypes {
Anonymous = 'Anonymous',
Basic = 'Basic',
X_API_KEY = 'X-API-Key',
Token = 'Token',
User = 'User',
FirebaseUser = 'FirebaseUser',
OAuth2 = 'OAuth2',
}
54 changes: 34 additions & 20 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import axios from 'axios';
import EventSource from 'eventsource';
import auth from './auth';
import { AuthTypes, BitloopsUser } from './definitions';

export enum AuthTypes {
Anonymous = 'Anonymous',
Basic = 'Basic',
X_API_KEY = 'X-API-Key',
Token = 'Token',
User = 'User',
FirebaseUser = 'FirebaseUser',
OAuth2 = 'OAuth2',
}
export { AuthTypes };

export interface IFirebaseUser {
accessToken: string;
Expand All @@ -20,17 +14,29 @@ export interface IAuthenticationOptions {
}

export interface IAPIAuthenticationOptions extends IAuthenticationOptions {
authenticationType: AuthTypes.X_API_KEY;
token: string;
refreshTokenFunction?: never;
}

export interface IFirebaseAuthenticationOptions extends IAuthenticationOptions {
authenticationType: AuthTypes.FirebaseUser;
providerId: string;
user: IFirebaseUser;
refreshTokenFunction?: () => Promise<string | null>;
}
export interface IBitloopsAuthenticationOptions extends IAuthenticationOptions {
authenticationType: AuthTypes.User;
providerId: string;
clientId: string;
token: string | null;
authChangeCallback: null | ((BitloopsUser) => void);
}

export type AuthenticationOptionsType = IFirebaseAuthenticationOptions | IAPIAuthenticationOptions;
export type AuthenticationOptionsType =
| IFirebaseAuthenticationOptions
| IAPIAuthenticationOptions
| IBitloopsAuthenticationOptions;

export type BitloopsConfig = {
apiKey: string;
Expand All @@ -39,6 +45,7 @@ export type BitloopsConfig = {
ssl?: boolean;
workspaceId: string;
messagingSenderId: string;
auth?: AuthenticationOptionsType;
};

/** Removes subscribe listener */
Expand All @@ -48,20 +55,24 @@ class Bitloops {
config: BitloopsConfig;
authType: AuthTypes;
authOptions: AuthenticationOptionsType | undefined;
auth = auth;
private subscribeConnection: EventSource;
private subscribeConnectionId: string = '';
private reconnectFreqSecs: number = 1;
private eventMap = new Map();
private static self: Bitloops;

constructor(config: BitloopsConfig) {
this.authOptions = config.auth;
this.config = config;
this.auth.setBitloops(this);
}

public static initialize(config: BitloopsConfig): Bitloops {
return new Bitloops(config);
}

public authenticate(options: IFirebaseAuthenticationOptions | IAPIAuthenticationOptions): void {
public authenticate(options: AuthenticationOptionsType): void {
this.authOptions = options;
}

Expand Down Expand Up @@ -136,6 +147,7 @@ class Bitloops {
}`;

const headers = this.getAuthHeaders();

const response = await axios.post<string>(
subscribeUrl,
{
Expand All @@ -145,22 +157,22 @@ class Bitloops {
{ headers }
);

if (!this.subscribeConnectionId) {
if (!this.subscribeConnectionId || this.subscribeConnectionId === '') {
this.subscribeConnectionId = response.data;
this.setupEventSource();
this.setupEventSource(true);
}

const listenerCb = (event: MessageEvent<any>) => {
callback(JSON.parse(event.data));
}
};

this.subscribeConnection.addEventListener(namedEvent, listenerCb);

return () => {
this.subscribeConnection.removeEventListener(namedEvent, listenerCb);
this.eventMap.delete(namedEvent);
if (this.eventMap.size === 0) this.subscribeConnection.close();
}
};
}

private getAuthHeaderValues(
Expand All @@ -180,7 +192,9 @@ class Bitloops {
case AuthTypes.Token:
throw Error('Unimplemented');
case AuthTypes.User:
throw Error('Unimplemented');
providerId = (authOptions as any).providerId;
token = '';
break;
case AuthTypes.FirebaseUser:
token = (authOptions as IFirebaseAuthenticationOptions).user?.accessToken;
providerId = (authOptions as IFirebaseAuthenticationOptions).providerId;
Expand Down Expand Up @@ -226,22 +240,22 @@ class Bitloops {
private async resubscribe() {
this.eventMap.forEach((callback, namedEvent) => {
this.subscribe(namedEvent, callback);
})
});
}

private setupEventSource() {
private setupEventSource(inititialRun = false) {
const url = `${this.httpSecure()}://${this.config.server}/bitloops/events/${this.subscribeConnectionId}`;

const headers = this.getAuthHeaders();
const eventSourceInitDict = { headers };

this.subscribeConnection = new EventSource(url, eventSourceInitDict);
this.resubscribe();
if (!inititialRun) this.resubscribe();

this.subscribeConnection.onopen = (e: any) => {
// console.log('Resetting retry timer...')
this.reconnectFreqSecs = 1;
}
};

this.subscribeConnection.onerror = (error: any) => {
this.subscribeConnection.close();
Expand Down
2 changes: 1 addition & 1 deletion src/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const test = async () => {
const workspaces = await bitloops.r('00cdd9c6-c6e9-40f2-b0c0-8903e33ef279', '8214cbd7-4741-46a4-bb28-ae7ad0f33cc4');
console.log(workspaces);

await bitloops.subscribe('workflowEvents.update', (data) => {
await bitloops.subscribe('workflow-events.update', (data) => {
console.log('workflow.Update received', data);
});

Expand Down
Loading

0 comments on commit 2213e66

Please sign in to comment.