How to change access token format using node oidc provider - node.js

I implemented node oidc Provider in my project and I got access_token also. but in access token in different format. how to change jwt token format and I added change access_token format to jwt but it could not work. How to resolve it this Issue and I need how to setup adapter config also.
Configuration.ts
const oidc = new Provider('http://localhost:3000', {
adapter:SequelizeAdapter,
clients: [
{
client_id: 'oidcCLIENT',
client_secret: '...',
grant_types: ['refresh_token', 'authorization_code'],
redirect_uris: ['http://sso-client.dev/providers/7/open_id', 'http://sso-client.dev/providers/8/open_id'],
}
],
interactions: {
url(ctx, interaction) { // eslint-disable-line no-unused-vars
return `/interaction/${interaction.uid}`;
},
},
cookies: {
keys: ['some secret key', 'and also the old rotated away some time ago', 'and one more'],
},
claims: {
address: ['address'],
email: ['email', 'email_verified'],
phone: ['phone_number', 'phone_number_verified'],
profile: ['birthdate', 'family_name', 'gender', 'given_name', 'locale', 'middle_name', 'name',
'nickname', 'picture', 'preferred_username', 'profile', 'updated_at', 'website', 'zoneinfo'],
},
format:{
AccessToken:'jwt'
},
features: {
devInteractions: { enabled: true }, // defaults to true
mTLS: {
enabled: true,
certificateBoundAccessTokens: true,
selfSignedTlsClientAuth: true,
getCertificate(ctx) {
return unescape(ctx.get('x-ssl-client-cert').replace(/\+/g, ' '));
},
certificateAuthorized(ctx) {
return ctx.get('x-ssl-client-verify') === 'SUCCESS';
},
certificateSubjectMatches(ctx, property, expected) {
if (property !== 'tls_client_auth_subject_dn') {
}
return ctx.get('x-ssl-client-s-dn') === expected;
},
},
claimsParameter: { enabled: true },
deviceFlow: { enabled: true },
dPoP: { enabled: true },
encryption: { enabled: true },
jwtUserinfo: { enabled: true },
introspection: { enabled: true },
registration: { enabled: true },
registrationManagement: { enabled: true, rotateRegistrationAccessToken: true },
jwtResponseModes: { enabled: true },
pushedAuthorizationRequests: { enabled: true },
requestObjects: {
request: true,
requestUri: true,
mode: 'strict',
},
// deviceFlow: { enabled: true }, // defaults to false
revocation: { enabled: true }, // defaults to false
userinfo: {enable:true}
},
findAccount: Account.findAccount,
issueRefreshToken: async (ctx, client, code) => {
return client.grantTypeAllowed('refresh_token') && (code.scopes.has('offline_access') || code.scopes.has('openid') || code.scopes.has('token'));
},
jwks: {
keys: [
{
d: 'VEZOsY07JTFzGTqv6cC2Y32vsfChind2I_TTuvV225_-0zrSej3XLRg8iE_u0-3GSgiGi4WImmTwmEgLo4Qp3uEcxCYbt4NMJC7fwT2i3dfRZjtZ4yJwFl0SIj8TgfQ8ptwZbFZUlcHGXZIr4nL8GXyQT0CK8wy4COfmymHrrUoyfZA154ql_OsoiupSUCRcKVvZj2JHL2KILsq_sh_l7g2dqAN8D7jYfJ58MkqlknBMa2-zi5I0-1JUOwztVNml_zGrp27UbEU60RqV3GHjoqwI6m01U7K0a8Q_SQAKYGqgepbAYOA-P4_TLl5KC4-WWBZu_rVfwgSENwWNEhw8oQ',
dp: 'E1Y-SN4bQqX7kP-bNgZ_gEv-pixJ5F_EGocHKfS56jtzRqQdTurrk4jIVpI-ZITA88lWAHxjD-OaoJUh9Jupd_lwD5Si80PyVxOMI2xaGQiF0lbKJfD38Sh8frRpgelZVaK_gm834B6SLfxKdNsP04DsJqGKktODF_fZeaGFPH0',
dq: 'F90JPxevQYOlAgEH0TUt1-3_hyxY6cfPRU2HQBaahyWrtCWpaOzenKZnvGFZdg-BuLVKjCchq3G_70OLE-XDP_ol0UTJmDTT-WyuJQdEMpt_WFF9yJGoeIu8yohfeLatU-67ukjghJ0s9CBzNE_LrGEV6Cup3FXywpSYZAV3iqc',
e: 'AQAB',
kty: 'RSA',
n: 'xwQ72P9z9OYshiQ-ntDYaPnnfwG6u9JAdLMZ5o0dmjlcyrvwQRdoFIKPnO65Q8mh6F_LDSxjxa2Yzo_wdjhbPZLjfUJXgCzm54cClXzT5twzo7lzoAfaJlkTsoZc2HFWqmcri0BuzmTFLZx2Q7wYBm0pXHmQKF0V-C1O6NWfd4mfBhbM-I1tHYSpAMgarSm22WDMDx-WWI7TEzy2QhaBVaENW9BKaKkJklocAZCxk18WhR0fckIGiWiSM5FcU1PY2jfGsTmX505Ub7P5Dz75Ygqrutd5tFrcqyPAtPTFDk8X1InxkkUwpP3nFU5o50DGhwQolGYKPGtQ-ZtmbOfcWQ',
p: '5wC6nY6Ev5FqcLPCqn9fC6R9KUuBej6NaAVOKW7GXiOJAq2WrileGKfMc9kIny20zW3uWkRLm-O-3Yzze1zFpxmqvsvCxZ5ERVZ6leiNXSu3tez71ZZwp0O9gys4knjrI-9w46l_vFuRtjL6XEeFfHEZFaNJpz-lcnb3w0okrbM',
q: '3I1qeEDslZFB8iNfpKAdWtz_Wzm6-jayT_V6aIvhvMj5mnU-Xpj75zLPQSGa9wunMlOoZW9w1wDO1FVuDhwzeOJaTm-Ds0MezeC4U6nVGyyDHb4CUA3ml2tzt4yLrqGYMT7XbADSvuWYADHw79OFjEi4T3s3tJymhaBvy1ulv8M',
qi: 'wSbXte9PcPtr788e713KHQ4waE26CzoXx-JNOgN0iqJMN6C4_XJEX-cSvCZDf4rh7xpXN6SGLVd5ibIyDJi7bbi5EQ5AXjazPbLBjRthcGXsIuZ3AtQyR0CEWNSdM7EyM5TRdyZQ9kftfz9nI03guW3iKKASETqX2vh0Z8XRjyU',
use: 'sig',
}, {
crv: 'P-256',
d: 'K9xfPv773dZR22TVUB80xouzdF7qCg5cWjPjkHyv7Ws',
kty: 'EC',
use: 'sig',
x: 'FWZ9rSkLt6Dx9E3pxLybhdM6xgR5obGsj5_pqmnz5J4',
y: '_n8G69C-A2Xl4xUW2lF0i8ZGZnk_KPYrhv4GbTGu5G4',
},
],
},
ttl: {
AccessToken: 24*60*60,//3600,
AuthorizationCode: 24*60*60,//600,
ClientCredentials: 24*60*60,//600,
DeviceCode: 24*60*60,//600,
IdToken: 24*60*60,//3600,
RefreshToken: 24*60*60,//1209600,
},
});

This is happening due to nod oidc provider version. With ver#7, instead of formats:{Access token:'jwt'} ( which worked perfectly in ver#6), you need to provide the resourceIndicators.
Follow this link to know more, Panva(Filip Skoken, author of the library) has given th detailed information.
Discussion on getting JWT token.:
https://github.com/panva/node-oidc-provider/discussions/959
Resource Indicators:
https://github.com/panva/node-oidc-provider/blob/main/docs/README.md#featuresresourceindicators

add the following methods under the features prop from config
resourceIndicators: {
defaultResource: (ctx, client, oneOf) => {
console.log('default resource', client);
return "http://example.com";
},
enabled: true,
getResourceServerInfo: (ctx, resourceIndicator, client) => {
console.log('get resource server info', client);
return ({
audience: 'solid',
accessTokenTTL: 2 * 60 * 60, // 2 hours
accessTokenFormat: 'jwt',
jwt: {
sign: { alg: 'ES256' },
},
});
},
useGrantedResource: (ctx, model) => {
// #param ctx - koa request context
// #param model - depending on the request's grant_type this can be either an AuthorizationCode, BackchannelAuthenticationRequest,
// RefreshToken, or DeviceCode model instance.
return true;
}
},

Related

NG0203 error for deployed microfrontend project

I am working on microfrontend. using module federation. shell and the remote projects have different repositories and are deployed on different servers. below are the code
shell project : calling the remote project through routing
{
path: 'analytics',
loadChildren: () => loadRemoteModule({
remoteEntry: `${environment.analyticsServerUrl}/remoteEntry.js`,
type: 'module',
exposedModule: './AnalyticsModule' })
.then(m => m.AppModule)
},
shell project : webpack.config.js
const webpack = require('webpack');
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
output: {
publicPath: 'http://localhost:4200/',
uniqueName: 'o360',
scriptType: 'text/javascript',
},
optimization: {
runtimeChunk: false,
},
plugins: [
new ModuleFederationPlugin({
remotes: {
analytics: 'http://localhost:4201',
},
shared: {
'#angular/core': {
eager: true,
singleton: true
},
'#angular/common': {
eager: true,
singleton: true
},
'#angular/router': {
eager: true,
singleton: true
},
'#ngxs/store': {
singleton: true,
eager: true
},
'social-login': {
singleton: true,
eager: true
},
},
}),
],
};
remote project :
webpack.config.js :
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const mf = require("#angular-architects/module-federation/webpack");
const path = require("path");
const share = mf.share;
const sharedMappings = new mf.SharedMappings();
sharedMappings.register(
path.join(__dirname, 'tsconfig.json'), [ /* mapped paths to share */ ]);
module.exports = {
output: {
uniqueName: "analytics",
publicPath: 'http://localhost:4201/'
},
optimization: {
runtimeChunk: false
},
resolve: {
alias: {
...sharedMappings.getAliases(),
}
},
experiments: {
outputModule: true
},
plugins: [
new ModuleFederationPlugin({
library: {
type: "module"
},
name: "analytics",
filename: "remoteEntry.js",
exposes: {
'./AnalyticsModule': './src/app/app.module.ts',
},
shared: share({
"#angular/core": {
singleton: true,
eager: true
},
"#angular/common": {
singleton: true,
eager: true
},
"#angular/common/http": {
singleton: true,
eager: true
},
"#angular/router": {
singleton: true,
eager: true
},
"shared-lib": {
singleton: true,
eager: true
},
...sharedMappings.getDescriptors()
})
}),
sharedMappings.getPlugin()
],
};
individually they are working fine after deployment to their respective servers but when I am trying to open the remote project through the shell project then below error is seen in the console.
Error: Uncaught (in promise): Error: NG0203
Error: NG0203
at Fy (remoteEntry.js:1:109371)
at Nt (remoteEntry.js:1:109522)
at e.ɵfac [as factory] (remoteEntry.js:1:256984)
at R1.hydrate (vendor.c5829c2ac166cc04.js:1:4138001)
at R1.get (vendor.c5829c2ac166cc04.js:1:4135925)
at vendor.c5829c2ac166cc04.js:1:4136752
at Set.forEach (<anonymous>)
at R1._resolveInjectorDefTypes (vendor.c5829c2ac166cc04.js:1:4136736)
at new Fx (vendor.c5829c2ac166cc04.js:1:4174507)
at db.create (vendor.c5829c2ac166cc04.js:1:4175218)
at z (polyfills.b96b59aa4b290638.js:1:15990)
at z (polyfills.b96b59aa4b290638.js:1:15525)
at polyfills.b96b59aa4b290638.js:1:16838
at v.invokeTask (polyfills.b96b59aa4b290638.js:1:7185)
at Object.onInvokeTask (vendor.c5829c2ac166cc04.js:1:4191629)
at v.invokeTask (polyfills.b96b59aa4b290638.js:1:7106)
at M.runTask (polyfills.b96b59aa4b290638.js:1:2580)
at _ (polyfills.b96b59aa4b290638.js:1:9200)
as NG0203 is an error related inject() function which I am not using anywhere in both applications but still getting this error so seems something is getting injected post loading of the module. I was exposing specific module analyticsModule before but later exposed the root module appModule but still the same error. tried using preloading and other solutions give on the internet but still the same error.

Can a push notification be send from the server without a client call?

I have a javascript file on my NodeJS server that runs at 00:00:00 and updates some fields in the database, if that happens I want to send out a push notification to some users. I've set this up in my Javascript file:
https://dev.to/devsmranjan/web-push-notification-with-web-push-angular-node-js-36de
const subscription = {
endpoint: '',
expirationTime: null,
keys: {
auth: '',
p256dh: '',
},
};
const payload = {
notification: {
title: 'Title',
body: 'This is my body',
icon: 'assets/icons/icon-384x384.png',
actions: [
{action: 'bar', title: 'Focus last'},
{action: 'baz', title: 'Navigate last'},
],
data: {
onActionClick: {
default: {operation: 'openWindow'},
bar: {
operation: 'focusLastFocusedOrOpen',
url: '/signin',
},
baz: {
operation: 'navigateLastFocusedOrOpen',
url: '/signin',
},
},
},
},
};
const options = {
vapidDetails: {
subject: 'mailto:example_email#example.com',
publicKey: process.env.REACT_APP_PUBLIC_VAPID_KEY,
privateKey: process.env.REACT_APP_PRIVATE_VAPID_KEY,
},
TTL: 60,
};
webpush.sendNotification(subscription, JSON.stringify(payload), options)
.then((_) => {
console.log(subscription);
console.log('SENT!!!');
console.log(_);
})
.catch((_) => {
console.log(subscription);
console.log(_);
});
But when I run the file I get the message:
{ endpoint: '', expirationTime: null, keys: { auth: '', p256dh: '' } } Error: You must pass in a subscription with at least an endpoint.
Which makes sense since the server has no idea about service workers etc. Any suggestions on how to proceed?

Next JS advanced feature Content Security policy format

I am adding security Policy headers to my application using Security headers advanced feature of next.js
Reference :https://nextjs.org/docs/advanced-features/security-headers
I am quiet clear of how to add the security policies to the next application except the Content Security policy,It does not show any format of the policy in the Documentation :
Taking help of next and developer.mozilla org I have some code in place,that I am attaching herewith. Can anybody help me with the correct format to define content-security policy in Next.JS
Code in Next.config.js
const cspPolicy = "default-src 'self';connect-src 'self','preview.contentful.com','*.flippenterprise.net', 'aq.flippenterprise.net','https://www.google-analytics.com','dpm.demdex.net','stats.g.doubleclick.net','lcljoefresh.sc.omtrdc.net','sfml.flippback.com','p.flipp.com','https://sentry.io/',";
const securityHeaders = [
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN'
},
{
key: 'Content-Security-Policy',
value: cspPolicy
}
];
const headers = {
async headers() {
return [
{
// Apply these headers to all routes in your application.
source: '/(.*)',
headers: securityHeaders,
},
]
},
}
const nextConfig = {
env: {
search_key: process.env.search_key,
appDynamicsKey: process.env.appDynamicsKey,
ENVIRONMENT: process.env.ENVIRONMENT,
},
webpack5:false,
// https://nextjs.org/docs#customizing-webpack-config
webpack: (webpackConfig) => {
const { module = {} } = webpackConfig;
return {
...webpackConfig,
module: {
...module,
rules: [
...(module.rules || []),
{
test: /\.(txt|png|woff|woff2|eot|ttf|gif|jpg|ico|svg)$/,
use: {
loader: 'file-loader',
options: {
name: '[name]_[hash].[ext]',
publicPath: '/_next/static/files',
outputPath: 'static/files',
},
},
},
{
test: /\.spec.jsx$/,
loader: 'ignore-loader',
},
],
},
};
},
};
const sassOptions = {
sassLoaderOptions: {
outputStyle: 'compressed',
},
};
module.exports = withPlugins(
[[withSass, sassOptions], [withFonts], [withCSS], [withBundleAnalyzer]],
nextConfig,
headers,
);

Apollo GraphQL Server - Access query params from cache plugin

I have an Apollo GraphQL server using the apollo-server-plugin-response-cache plugin and I need to determine whether or not I'm going to write to the cache based on incoming parameters. I have the plugin set up and I'm using the shouldWriteToCache hook. I can print out the GraphQLRequestContext object that gets passed into the hook, and I can see the full request source, but request.variables is empty. Other than parsing the query itself, how can I access the actual params for the resolver in this hook? (In the example below, I need the value of param2.)
Apollo Server:
new ApolloServer({
introspection: true,
playground: true,
subscriptions: false,
typeDefs,
resolvers,
cacheControl: {
defaultMaxAge: 60
},
plugins: [
apolloServerPluginResponseCache({
cache, // This is a "apollo-server-cache-redis" instance
shouldWriteToCache: (requestContext) => {
// I get a lot of info here, including the source query, but not the
// parsed out query variables
console.log(requestContext.request);
// What I want to do here is:
return !context.request.variables.param2
// but `variables` is empty, and I can't see that value parsed anywhere else
}
})
]
})
Here is my resolver:
export async function exapi(variables, context) {
// in here I use context.param1 and context.param2
// ...
}
I have also tried:
export async function exapi(variables, { param1, param2 }) {
// ...
}
Here is what I get logged out from the code above:
{
query: '{\n' +
' exapi(param1: "value1", param2: true) {\n' +
' records\n' +
' }\n' +
'}\n',
operationName: null,
variables: {}, // <-- this is empty?! How can I get param2's value??
extensions: undefined,
http: Request {
size: 0,
timeout: 0,
follow: 20,
compress: true,
counter: 0,
agent: undefined,
[Symbol(Body internals)]: { body: null, disturbed: false, error: null },
[Symbol(Request internals)]: {
method: 'POST',
redirect: 'follow',
headers: [Headers],
parsedURL: [Url],
signal: null
}
}
}
If you didn't provide variables for GraphQL query, you could get the arguments from the GraphQL query string via ArgumentNode of AST
If you provide variables for GraphQL query, you will get them from requestContext.request.variables.
E.g.
server.js:
import apolloServerPluginResponseCache from 'apollo-server-plugin-response-cache';
import { ApolloServer, gql } from 'apollo-server';
import { RedisCache } from 'apollo-server-cache-redis';
const typeDefs = gql`
type Query {
exapi(param1: String, param2: Boolean): String
}
`;
const resolvers = {
Query: {
exapi: (_, { param1, param2 }) => 'teresa teng',
},
};
const cache = new RedisCache({ host: 'localhost', port: 6379 });
const server = new ApolloServer({
introspection: true,
playground: true,
subscriptions: false,
typeDefs,
resolvers,
cacheControl: {
defaultMaxAge: 60,
},
plugins: [
apolloServerPluginResponseCache({
cache,
shouldWriteToCache: (requestContext) => {
console.log(requestContext.document.definitions[0].selectionSet.selections[0].arguments);
return true;
},
}),
],
});
server.listen().then(({ url }) => console.log(`🚀 Server ready at ${url}`));
GraphQL query:
query{
exapi(param1: "value1", param2: true)
}
Server logs print param1 and param2 arguments:
🚀 Server ready at http://localhost:4000/
[]
[ { kind: 'Argument',
name: { kind: 'Name', value: 'param1', loc: [Object] },
value:
{ kind: 'StringValue',
value: 'value1',
block: false,
loc: [Object] },
loc: { start: 15, end: 31 } },
{ kind: 'Argument',
name: { kind: 'Name', value: 'param2', loc: [Object] },
value: { kind: 'BooleanValue', value: true, loc: [Object] },
loc: { start: 33, end: 45 } } ]

Sequelize and response request GraphQL

I try to have a response on my request GraphQL.
I tried many things but currently I have always the Sequence response, and no the Buckets response (belongs To relation).
I have 2 tables :
Sequence [id | is_active]
Bucket [id | fk_language_id | fk_sequence_id | is_active]
model/sequence.js
'use strict';
module.exports = (sequelize, DataTypes) => {
// define sequence
const Sequence = sequelize.define('sequence', {
is_active: {type: DataTypes.BOOLEAN}
});
Sequence.associate = function (models) {
models.Sequence.hasMany(models.Bucket, {
foreignKey: 'fk_sequence_id'
});
return Sequence;
};
model/bucket.js
'use strict';
module.exports = (sequelize, DataTypes) => {
const Bucket = sequelize.define('bucket', {
code : {type: DataTypes.STRING},
is_active: {type: DataTypes.BOOLEAN}
});
Bucket.associate = function (models) {
models.Bucket.belongsTo(models.Language, {
foreignKey: 'fk_language_id',
});
models.Bucket.belongsTo(models.Sequence, {
foreignKey: 'fk_sequence_id',
});
};
return Bucket;
};
schema.js
# Sequence
type Sequence {
id: Int!,
code: String,
buckets: [Bucket],
is_active: Boolean
}
# Bucket
type Bucket {
id: Int!,
code: String
blocks: [Block]
is_active: Boolean
}
# SequenceInput
input SequenceInput {
buckets: [BucketInput],
is_active: Boolean
}
# BucketInput
input BucketInput {
code: String,
fk_language_id: Int,
fk_sequence_id: Int,
is_active: Boolean
}
type Query {
sequences: [Sequence]
sequence(id: Int): Sequence
buckets: [Bucket]
bucket(id: Int): Bucket
}
type Mutation {
createSequence(input: SequenceInput): Sequence,
}
Request GraphQL
mutation {
createSequence(input: {
is_active: false,
buckets: [
{fk_language_id: 2, code: "Test"}
]
}) {
is_active,
buckets {
id,
code
}
}
}
But I have this result, the Buckets doesn't load :
{
"data": {
"createSequence": {
"is_active": false,
"buckets": []
}
}
}
my mutation :
...
Sequence : {
buckets(sequence) {
return models.Bucket.findAll({
where: {id: sequence.id}
});
},
...
},
...
Mutation : {
createSequence(_, {input}) {
let sequenceId = 0;
// Create Sequence
return models.Sequence.create(input)
.then((sequence) => {
sequenceId = sequence.id;
console.log('sequence created');
// Create Bucket
// Foreach on buckets
return Promise.map(input.buckets, function (bucket) {
bucket.fk_sequence_id = sequenceId;
console.log('bucket created');
return models.Bucket.create(bucket);
})
})
.then(() => {
console.log('load created', sequenceId);
return models.Sequence.findOne({
where : {id: sequenceId},
include: [
{
model: models.Bucket,
where: { fk_sequence_id: sequenceId }
}
]
}).then((response) => {
console.log(response);
return response;
})
});
},
}
The final console.log show many informations...
sequence {
dataValues:
{ id: 416,
is_active: false,
created_at: 2019-03-29T20:33:56.196Z,
updated_at: 2019-03-29T20:33:56.196Z,
buckets: [ [Object] ] },
_previousDataValues:
{ id: 416,
is_active: false,
created_at: 2019-03-29T20:33:56.196Z,
updated_at: 2019-03-29T20:33:56.196Z,
buckets: [ [Object] ] },
_changed: {},
_modelOptions:
{ timestamps: true,
validate: {},
freezeTableName: true,
underscored: false,
paranoid: false,
rejectOnEmpty: false,
whereCollection: { id: 416 },
schema: null,
schemaDelimiter: '',
defaultScope: {},
scopes: {},
indexes: [],
name: { plural: 'sequences', singular: 'sequence' },
omitNull: false,
createdAt: 'created_at',
updatedAt: 'updated_at',
sequelize:
Sequelize {
options: [Object],
config: [Object],
dialect: [Object],
queryInterface: [Object],
models: [Object],
modelManager: [Object],
connectionManager: [Object],
importCache: [Object],
test: [Object] },
hooks: {} },
_options:
{ isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
include: [ [Object] ],
includeNames: [ 'buckets' ],
includeMap: { buckets: [Object] },
includeValidated: true,
attributes: [ 'id', 'is_active', 'created_at', 'updated_at' ],
raw: true },
isNewRecord: false,
buckets:
[ bucket {
dataValues: [Object],
_previousDataValues: [Object],
_changed: {},
_modelOptions: [Object],
_options: [Object],
isNewRecord: false } ] }
Your mutation resolver returns a Promise, which resolves into a Model instance. The promise in question is returned on this line:
return models.Sequence.create(input)
.
As such, the server will wait until that promise is resolved before passing the value forward. Other actions were also waiting on that promise, but they were not the promises returned, so they will not be waited for.
All you have to do is wait for all of your operations to finish before resolving your promise.
createSequence: async (parent, { input }) => {
const sequence = await models.Sequence.create({
is_active: input.is_active
})
if (!input.buckets) return sequence
// You may have to modify your Sequence.buckets resolver to avoid fetching buckets again.
sequence.buckets = await Promise.all(input.buckets.map(bucket => {
// You can avoid these if checks by implementing stricter input types.
// e.g. buckets: [BucketInput!]!
if (!bucket) return null
return models.Bucket.create({
...bucket,
fk_sequence_id: sequence.id
})
}))
return sequence
}
Also, make sure your Sequence.buckets resolver isn't overwriting buckets with faulty data. The resolver you've provided will try to match bucket primary keys with a sequence primary key instead of matching the correct foreign keys with a primary key.
Here's a resolver that will work:
buckets: (parent) => (
parent.buckets // This line may conflict with some of your code and cause problems.
|| models.Bucket.findAll({
where: {fk_sequence_id: parent.id}
})
)

Resources