forked from adonisjs-community/ally-starter-kit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
alexi
committed
Aug 9, 2022
1 parent
b124908
commit e81ee72
Showing
11 changed files
with
600 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,6 @@ test/__app | |
*.sublime-project | ||
*.sublime-workspace | ||
*.log | ||
build | ||
dist | ||
# build | ||
# dist | ||
shrinkwrap.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
The package has been configured successfully! | ||
|
||
Make sure to first define the mapping inside the `contracts/ally.ts` file as follows. | ||
|
||
```ts | ||
import { OktaDriver, OktaDriverConfig } from 'ally-custom-driver/build/standalone' | ||
|
||
declare module '@ioc:Adonis/Addons/Ally' { | ||
interface SocialProviders { | ||
// ... other mappings | ||
OktaDriver: { | ||
config: OktaDriverConfig | ||
implementation: OktaDriver | ||
} | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/// <reference types="@adonisjs/application/build/adonis-typings" /> | ||
import type { ApplicationContract } from '@ioc:Adonis/Core/Application'; | ||
export default class OktaDriverProvider { | ||
protected app: ApplicationContract; | ||
constructor(app: ApplicationContract); | ||
boot(): Promise<void>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
"use strict"; | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
var desc = Object.getOwnPropertyDescriptor(m, k); | ||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { | ||
desc = { enumerable: true, get: function() { return m[k]; } }; | ||
} | ||
Object.defineProperty(o, k2, desc); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
class OktaDriverProvider { | ||
constructor(app) { | ||
this.app = app; | ||
} | ||
async boot() { | ||
const Ally = this.app.container.resolveBinding('Adonis/Addons/Ally'); | ||
const { OktaDriver } = await Promise.resolve().then(() => __importStar(require('../src/Okta'))); | ||
Ally.extend('Okta', (_, __, config, ctx) => { | ||
return new OktaDriver(ctx, config); | ||
}); | ||
} | ||
} | ||
exports.default = OktaDriverProvider; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
/// <reference types="@adonisjs/ally" /> | ||
/// <reference types="@adonisjs/http-server/build/adonis-typings" /> | ||
/// <reference types="@adonisjs/ally" /> | ||
import type { AllyUserContract } from '@ioc:Adonis/Addons/Ally'; | ||
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; | ||
import { Oauth2Driver, ApiRequest } from '@adonisjs/ally/build/standalone'; | ||
import { UserFields } from '../helpers/AllyUser'; | ||
export declare type OktaDriverAccessToken = { | ||
token: string; | ||
type: 'bearer'; | ||
}; | ||
export declare type OktaDriverScopes = 'openid' | 'profile' | 'email'; | ||
export declare type OktaDriverConfig = { | ||
driver: 'Okta'; | ||
clientId: string; | ||
callbackUrl: string; | ||
clientSecret: string; | ||
issuer: string; | ||
domain: string; | ||
scopes: OktaDriverScopes[]; | ||
}; | ||
export declare type UserInfo = { | ||
sub: string; | ||
name: string; | ||
locale: string; | ||
email: string; | ||
preferred_username: string; | ||
given_name: string; | ||
family_name: string; | ||
zoneinfo: string; | ||
email_verified: boolean; | ||
}; | ||
/** | ||
* Driver implementation. It is mostly configuration driven except the user calls | ||
*/ | ||
export declare class OktaDriver extends Oauth2Driver<OktaDriverAccessToken, OktaDriverScopes> { | ||
config: OktaDriverConfig; | ||
/** | ||
* The URL for the redirect request. The user will be redirected on this page | ||
* to authorize the request. | ||
* | ||
* Do not define query strings in this URL. | ||
*/ | ||
protected authorizeUrl: string; | ||
/** | ||
* The URL to hit to exchange the authorization code for the access token | ||
* | ||
* Do not define query strings in this URL. | ||
*/ | ||
protected accessTokenUrl: string; | ||
/** | ||
* The URL to hit to get the user details | ||
* | ||
* Do not define query strings in this URL. | ||
*/ | ||
protected userInfoUrl: string; | ||
/** | ||
* The param name for the authorization code. Read the documentation of your oauth | ||
* provider and update the param name to match the query string field name in | ||
* which the oauth provider sends the authorization_code post redirect. | ||
*/ | ||
protected codeParamName: string; | ||
/** | ||
* The param name for the error. Read the documentation of your oauth provider and update | ||
* the param name to match the query string field name in which the oauth provider sends | ||
* the error post redirect | ||
*/ | ||
protected errorParamName: string; | ||
/** | ||
* Cookie name for storing the CSRF token. Make sure it is always unique. So a better | ||
* approach is to prefix the oauth provider name to `oauth_state` value. For example: | ||
* For example: "facebook_oauth_state" | ||
*/ | ||
protected stateCookieName: string; | ||
/** | ||
* Parameter name to be used for sending and receiving the state from. | ||
* Read the documentation of your oauth provider and update the param | ||
* name to match the query string used by the provider for exchanging | ||
* the state. | ||
*/ | ||
protected stateParamName: string; | ||
/** | ||
* Parameter name for sending the scopes to the oauth provider. | ||
*/ | ||
protected scopeParamName: string; | ||
/** | ||
* The separator indentifier for defining multiple scopes | ||
*/ | ||
protected scopesSeparator: string; | ||
protected issuer: string; | ||
protected domain: string; | ||
protected clientId: string; | ||
constructor(ctx: HttpContextContract, config: OktaDriverConfig); | ||
/** | ||
* Optionally configure the authorization redirect request. The actual request | ||
* is made by the base implementation of "Oauth2" driver and this is a | ||
* hook to pre-configure the request. | ||
*/ | ||
/** | ||
* Optionally configure the access token request. The actual request is made by | ||
* the base implementation of "Oauth2" driver and this is a hook to pre-configure | ||
* the request | ||
*/ | ||
/** | ||
* Update the implementation to tell if the error received during redirect | ||
* means "ACCESS DENIED". | ||
*/ | ||
accessDenied(): boolean; | ||
getCode(): string | null; | ||
protected buildAllyUser(userProfile: any, accessTokenResponse: any): UserFields; | ||
/** | ||
* Get the user details by query the provider API. This method must return | ||
* the access token and the user details both. Checkout the google | ||
* implementation for same. | ||
* | ||
* https://github.com/adonisjs/ally/blob/develop/src/Drivers/Google/index.ts#L191-L199 | ||
*/ | ||
user(_callback?: (request: ApiRequest) => void): Promise<AllyUserContract<OktaDriverAccessToken>>; | ||
/** | ||
* Returns the HTTP request with the authorization header set | ||
*/ | ||
protected getAuthenticatedRequest(url: string, token: string): ApiRequest; | ||
protected getUserInfo(accessToken: string, callback?: (request: ApiRequest) => void): Promise<UserFields>; | ||
userFromToken(accessToken: string): Promise<AllyUserContract<OktaDriverAccessToken>>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
"use strict"; | ||
/* | ||
|-------------------------------------------------------------------------- | ||
| Ally Oauth driver | ||
|-------------------------------------------------------------------------- | ||
| | ||
| This is a dummy implementation of the Oauth driver. Make sure you | ||
| | ||
| - Got through every line of code | ||
| - Read every comment | ||
| | ||
*/ | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.OktaDriver = void 0; | ||
const standalone_1 = require("@adonisjs/ally/build/standalone"); | ||
const jwt_verifier_1 = __importDefault(require("@okta/jwt-verifier")); | ||
const lodash_1 = __importDefault(require("lodash")); | ||
const AllyUser_1 = __importDefault(require("../helpers/AllyUser")); | ||
/** | ||
* Driver implementation. It is mostly configuration driven except the user calls | ||
*/ | ||
class OktaDriver extends standalone_1.Oauth2Driver { | ||
constructor(ctx, config) { | ||
super(ctx, config); | ||
this.config = config; | ||
/** | ||
* The URL for the redirect request. The user will be redirected on this page | ||
* to authorize the request. | ||
* | ||
* Do not define query strings in this URL. | ||
*/ | ||
this.authorizeUrl = ''; | ||
/** | ||
* The URL to hit to exchange the authorization code for the access token | ||
* | ||
* Do not define query strings in this URL. | ||
*/ | ||
this.accessTokenUrl = ''; | ||
/** | ||
* The URL to hit to get the user details | ||
* | ||
* Do not define query strings in this URL. | ||
*/ | ||
this.userInfoUrl = ''; | ||
/** | ||
* The param name for the authorization code. Read the documentation of your oauth | ||
* provider and update the param name to match the query string field name in | ||
* which the oauth provider sends the authorization_code post redirect. | ||
*/ | ||
this.codeParamName = 'code'; | ||
/** | ||
* The param name for the error. Read the documentation of your oauth provider and update | ||
* the param name to match the query string field name in which the oauth provider sends | ||
* the error post redirect | ||
*/ | ||
this.errorParamName = 'error'; | ||
/** | ||
* Cookie name for storing the CSRF token. Make sure it is always unique. So a better | ||
* approach is to prefix the oauth provider name to `oauth_state` value. For example: | ||
* For example: "facebook_oauth_state" | ||
*/ | ||
this.stateCookieName = 'OktaDriver_oauth_state'; | ||
/** | ||
* Parameter name to be used for sending and receiving the state from. | ||
* Read the documentation of your oauth provider and update the param | ||
* name to match the query string used by the provider for exchanging | ||
* the state. | ||
*/ | ||
this.stateParamName = 'state'; | ||
/** | ||
* Parameter name for sending the scopes to the oauth provider. | ||
*/ | ||
this.scopeParamName = 'scope'; | ||
/** | ||
* The separator indentifier for defining multiple scopes | ||
*/ | ||
this.scopesSeparator = ','; | ||
this.issuer = ''; | ||
this.domain = ''; | ||
this.clientId = ''; | ||
/** | ||
* Extremely important to call the following method to clear the | ||
* state set by the redirect request. | ||
* | ||
* DO NOT REMOVE THE FOLLOWING LINE | ||
*/ | ||
this.loadState(); | ||
this.domain = config.domain; | ||
this.issuer = config.issuer; | ||
this.clientId = config.clientId; | ||
// this.callback = config.redirectUri | ||
} | ||
/** | ||
* Optionally configure the authorization redirect request. The actual request | ||
* is made by the base implementation of "Oauth2" driver and this is a | ||
* hook to pre-configure the request. | ||
*/ | ||
// protected configureRedirectRequest(request: RedirectRequest<OktaDriverScopes>) {} | ||
/** | ||
* Optionally configure the access token request. The actual request is made by | ||
* the base implementation of "Oauth2" driver and this is a hook to pre-configure | ||
* the request | ||
*/ | ||
// protected configureAccessTokenRequest(request: ApiRequest) {} | ||
/** | ||
* Update the implementation to tell if the error received during redirect | ||
* means "ACCESS DENIED". | ||
*/ | ||
accessDenied() { | ||
return this.ctx.request.input('error') === 'user_denied'; | ||
} | ||
getCode() { | ||
return this.ctx.request.input(this.codeParamName, null); | ||
} | ||
buildAllyUser(userProfile, accessTokenResponse) { | ||
const allyUserBuilder = new AllyUser_1.default(); | ||
const expires = lodash_1.default.get(accessTokenResponse, 'expiresAt'); | ||
allyUserBuilder | ||
.setOriginal(userProfile) | ||
.setFields(userProfile.sub, userProfile.given_name, userProfile.family_name, userProfile.email, null, userProfile.email_verified ? 'verified' : 'unverified') | ||
.setToken(accessTokenResponse.accessToken, null, null, expires ? Number(expires) : null); | ||
const user = allyUserBuilder.toJSON(); | ||
return user; | ||
} | ||
/** | ||
* Get the user details by query the provider API. This method must return | ||
* the access token and the user details both. Checkout the google | ||
* implementation for same. | ||
* | ||
* https://github.com/adonisjs/ally/blob/develop/src/Drivers/Google/index.ts#L191-L199 | ||
*/ | ||
async user(_callback) { | ||
const accessTokenResponse = this.ctx.request.input('accessToken'); | ||
const errorMessage = 'Okta login failed'; | ||
if (!accessTokenResponse) | ||
throw new Error(errorMessage); | ||
const audience = this.ctx.request.input('oktaAud') || 'api://default'; | ||
if (this.ctx.request.input('oktaDomain') && this.ctx.request.input('oktaClientId')) { | ||
this.issuer = | ||
this.ctx.request.input('oktaIssuer') || | ||
`https://${this.ctx.request.input('oktaDomain')}/oauth2/default`; | ||
this.domain = this.ctx.request.input('oktaDomain'); | ||
this.clientId = this.ctx.request.input('oktaClientId'); | ||
} | ||
try { | ||
const oktaJwtVerifier = new jwt_verifier_1.default({ | ||
issuer: this.issuer, | ||
clientId: this.clientId, | ||
assertClaims: { | ||
cid: this.clientId, | ||
aud: audience, | ||
}, | ||
}); | ||
await oktaJwtVerifier.verifyAccessToken(accessTokenResponse.value, audience); | ||
const userProfile = await this.getUserInfo(accessTokenResponse.value); | ||
return { | ||
...userProfile, | ||
token: accessTokenResponse.value, | ||
}; | ||
} | ||
catch (e) { | ||
throw new Error(errorMessage); | ||
} | ||
} | ||
/** | ||
* Returns the HTTP request with the authorization header set | ||
*/ | ||
getAuthenticatedRequest(url, token) { | ||
const request = this.httpClient(url); | ||
request.header('Authorization', `Bearer ${token}`); | ||
request.header('Accept', 'application/json'); | ||
request.header('x-li-format', 'json'); | ||
request.parseAs('json'); | ||
return request; | ||
} | ||
async getUserInfo(accessToken, callback) { | ||
// User Info | ||
const userRequest = this.getAuthenticatedRequest(`${this.issuer}/v1/userinfo`, accessToken); | ||
const accessTokenResponse = { | ||
accessToken, | ||
}; | ||
if (typeof callback === 'function') { | ||
callback(userRequest); | ||
} | ||
const userBody = await userRequest.get(); | ||
return this.buildAllyUser(userBody, accessTokenResponse); | ||
} | ||
async userFromToken(accessToken) { | ||
const user = {}; | ||
return { | ||
...user, | ||
token: { token: accessToken, type: 'bearer' }, | ||
}; | ||
} | ||
} | ||
exports.OktaDriver = OktaDriver; |
Oops, something went wrong.