# Archived: Nova IdP CloudFormation Template (v1.28)

> **Archived at v1.29.0** — the active path is `terraform apply` in
> `nova-platform-ops`. Deletion is a follow-up after Terraform parity
> is verified (REQ-369 AC 3, spec §7.5). This template is read-only
> reference; do not modify it. The `nova idp setup --apply` command
> now delegates to `terraform apply` (see `nova/idp/setup.py`).

This is the verbatim output of `generate_template()` from
`core/lambda/nova_idp_cfn.py` (the composition of the DynamoDB snippet
from `core/lambda/nova_idp_auth_cfn.py` + the KMS signing key + the
three IdP Lambdas + their IAM roles + function URLs). It was the active
provisioning path through v1.28; from v1.29 the operator runs
`terraform apply` in the `nova-platform-ops` checkout and `nova idp
setup --apply` delegates to it. The CFN generation code is retained as
read-only reference and emits a `DeprecationWarning` when the CFN
fallback path is invoked (terraform absent from PATH).

```json
{
  "Resources": {
    "NovaUsersTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "TableName": "nova-users",
        "BillingMode": "PAY_PER_REQUEST",
        "KeySchema": [
          {
            "AttributeName": "user_id",
            "KeyType": "HASH"
          }
        ],
        "AttributeDefinitions": [
          {
            "AttributeName": "user_id",
            "AttributeType": "S"
          },
          {
            "AttributeName": "email",
            "AttributeType": "S"
          }
        ],
        "GlobalSecondaryIndexes": [
          {
            "IndexName": "email-index",
            "KeySchema": [
              {
                "AttributeName": "email",
                "KeyType": "HASH"
              }
            ],
            "Projection": {
              "ProjectionType": "ALL"
            }
          }
        ],
        "PointInTimeRecoverySpecification": {
          "PointInTimeRecoveryEnabled": true
        },
        "AttributeShape": {
          "user_id": "String",
          "email": "String",
          "password_hash": "String",
          "owner": "String",
          "roles": "List",
          "created_at": "String"
        }
      }
    },
    "NovaSessionsTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "TableName": "nova-sessions",
        "BillingMode": "PAY_PER_REQUEST",
        "KeySchema": [
          {
            "AttributeName": "session_id",
            "KeyType": "HASH"
          }
        ],
        "AttributeDefinitions": [
          {
            "AttributeName": "session_id",
            "AttributeType": "S"
          },
          {
            "AttributeName": "user_id",
            "AttributeType": "S"
          }
        ],
        "GlobalSecondaryIndexes": [
          {
            "IndexName": "user_id-index",
            "KeySchema": [
              {
                "AttributeName": "user_id",
                "KeyType": "HASH"
              }
            ],
            "Projection": {
              "ProjectionType": "ALL"
            }
          }
        ],
        "TimeToLiveSpecification": {
          "AttributeName": "expires_at",
          "Enabled": true
        },
        "AttributeShape": {
          "session_id": "String",
          "user_id": "String",
          "expires_at": "String (epoch seconds, TTL)",
          "created_at": "String (ISO-8601)"
        }
      }
    },
    "NovaPasswordResetsTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "TableName": "nova-password-resets",
        "BillingMode": "PAY_PER_REQUEST",
        "KeySchema": [
          {
            "AttributeName": "reset_token",
            "KeyType": "HASH"
          }
        ],
        "AttributeDefinitions": [
          {
            "AttributeName": "reset_token",
            "AttributeType": "S"
          }
        ],
        "TimeToLiveSpecification": {
          "AttributeName": "expires_at",
          "Enabled": true
        },
        "AttributeShape": {
          "reset_token": "String",
          "user_id": "String",
          "expires_at": "String (epoch seconds, TTL; 15 min)"
        }
      }
    },
    "NovaPatsTable": {
      "Type": "AWS::DynamoDB::Table",
      "Properties": {
        "TableName": "nova-pats",
        "BillingMode": "PAY_PER_REQUEST",
        "KeySchema": [
          {
            "AttributeName": "jti",
            "KeyType": "HASH"
          }
        ],
        "AttributeDefinitions": [
          {
            "AttributeName": "jti",
            "AttributeType": "S"
          },
          {
            "AttributeName": "sub",
            "AttributeType": "S"
          },
          {
            "AttributeName": "pat_hash",
            "AttributeType": "S"
          }
        ],
        "GlobalSecondaryIndexes": [
          {
            "IndexName": "sub-index",
            "KeySchema": [
              {
                "AttributeName": "sub",
                "KeyType": "HASH"
              }
            ],
            "Projection": {
              "ProjectionType": "ALL"
            }
          },
          {
            "IndexName": "pat_hash-index",
            "KeySchema": [
              {
                "AttributeName": "pat_hash",
                "KeyType": "HASH"
              }
            ],
            "Projection": {
              "ProjectionType": "ALL"
            }
          }
        ],
        "TimeToLiveSpecification": {
          "AttributeName": "expires_at",
          "Enabled": true
        },
        "AttributeShape": {
          "jti": "String (PK)",
          "sub": "String (GSI1; subject / user_id)",
          "pat_hash": "String (GSI2; SHA-256 of the PAT for lookup)",
          "status": "String (active|revoked)",
          "issued_at": "String (ISO-8601)",
          "expires_at": "String (epoch seconds, TTL)",
          "revoked_at": "String (ISO-8601, present iff status=revoked)",
          "claims": "Map (JWT claims payload)"
        }
      }
    },
    "NovaOidcSigningKey": {
      "Type": "AWS::KMS::Key",
      "Properties": {
        "Description": "Nova OIDC token signing key (REQ-337, ECC_NIST_P256)",
        "KeySpec": "ECC_NIST_P256",
        "KeyUsage": "SIGN_VERIFY",
        "KeyPolicy": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "AWS": {
                  "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root"
                }
              },
              "Action": "kms:*",
              "Resource": "*"
            }
          ]
        }
      }
    },
    "NovaOidcSigningKeyAlias": {
      "Type": "AWS::KMS::Alias",
      "Properties": {
        "AliasName": "alias/nova-oidc-signing",
        "TargetKeyId": {
          "Fn::GetAtt": "NovaOidcSigningKey.Arn"
        }
      }
    },
    "NovaIdpAuthRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": {
                  "Fn::Sub": "lambda.${AWS::Region}.amazonaws.com"
                }
              },
              "Action": "sts:AssumeRole"
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": "NovaIdpAuthPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                  ],
                  "Resource": {
                    "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogGroup"
                  ],
                  "Resource": {
                    "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "dynamodb:GetItem",
                    "dynamodb:PutItem",
                    "dynamodb:UpdateItem",
                    "dynamodb:Query",
                    "dynamodb:DeleteItem"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/nova-users"
                    },
                    {
                      "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/nova-sessions"
                    },
                    {
                      "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/nova-password-resets"
                    }
                  ]
                }
              ]
            }
          }
        ]
      }
    },
    "NovaIdpTokenVendRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": {
                  "Fn::Sub": "lambda.${AWS::Region}.amazonaws.com"
                }
              },
              "Action": "sts:AssumeRole"
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": "NovaIdpTokenVendPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                  ],
                  "Resource": {
                    "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogGroup"
                  ],
                  "Resource": {
                    "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "dynamodb:GetItem",
                    "dynamodb:PutItem",
                    "dynamodb:UpdateItem",
                    "dynamodb:Query",
                    "dynamodb:DeleteItem"
                  ],
                  "Resource": [
                    {
                      "Fn::Sub": "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/nova-pats"
                    }
                  ]
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "kms:Sign",
                    "kms:GetPublicKey",
                    "kms:DescribeKey"
                  ],
                  "Resource": {
                    "Fn::GetAtt": "NovaOidcSigningKey.Arn"
                  }
                }
              ]
            }
          }
        ]
      }
    },
    "NovaIdpJwksRole": {
      "Type": "AWS::IAM::Role",
      "Properties": {
        "AssumeRolePolicyDocument": {
          "Version": "2012-10-17",
          "Statement": [
            {
              "Effect": "Allow",
              "Principal": {
                "Service": {
                  "Fn::Sub": "lambda.${AWS::Region}.amazonaws.com"
                }
              },
              "Action": "sts:AssumeRole"
            }
          ]
        },
        "Policies": [
          {
            "PolicyName": "NovaIdpJwksPolicy",
            "PolicyDocument": {
              "Version": "2012-10-17",
              "Statement": [
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogStream",
                    "logs:PutLogEvents"
                  ],
                  "Resource": {
                    "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "logs:CreateLogGroup"
                  ],
                  "Resource": {
                    "Fn::Sub": "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
                  }
                },
                {
                  "Effect": "Allow",
                  "Action": [
                    "kms:Sign",
                    "kms:GetPublicKey",
                    "kms:DescribeKey"
                  ],
                  "Resource": {
                    "Fn::GetAtt": "NovaOidcSigningKey.Arn"
                  }
                }
              ]
            }
          }
        ]
      }
    },
    "NovaIdpAuthFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "nova_idp_auth.lambda_handler",
        "Runtime": "python3.12",
        "MemorySize": 512,
        "Timeout": 30,
        "Role": {
          "Fn::GetAtt": [
            "NovaIdpAuthRole",
            "Arn"
          ]
        },
        "Environment": {
          "Variables": {
            "NOVA_USERS_TABLE": "nova-users",
            "NOVA_SESSIONS_TABLE": "nova-sessions",
            "NOVA_PASSWORD_RESETS_TABLE": "nova-password-resets",
            "NOVA_PATS_TABLE": "nova-pats"
          }
        },
        "Code": {
          "ZipFile": "def lambda_handler(event, context):\n    return {}"
        }
      }
    },
    "NovaIdpTokenVendFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "nova_idp_token_vend.lambda_handler",
        "Runtime": "python3.12",
        "MemorySize": 512,
        "Timeout": 30,
        "Role": {
          "Fn::GetAtt": [
            "NovaIdpTokenVendRole",
            "Arn"
          ]
        },
        "Environment": {
          "Variables": {
            "NOVA_USERS_TABLE": "nova-users",
            "NOVA_SESSIONS_TABLE": "nova-sessions",
            "NOVA_PASSWORD_RESETS_TABLE": "nova-password-resets",
            "NOVA_PATS_TABLE": "nova-pats",
            "NOVA_OIDC_KMS_KEY_ID": "alias/nova-oidc-signing"
          }
        },
        "Code": {
          "ZipFile": "def lambda_handler(event, context):\n    return {}"
        }
      }
    },
    "NovaIdpJwksFunction": {
      "Type": "AWS::Lambda::Function",
      "Properties": {
        "Handler": "nova_idp_jwks.lambda_handler",
        "Runtime": "python3.12",
        "MemorySize": 256,
        "Timeout": 30,
        "Role": {
          "Fn::GetAtt": [
            "NovaIdpJwksRole",
            "Arn"
          ]
        },
        "Environment": {
          "Variables": {
            "NOVA_OIDC_KMS_KEY_ID": "alias/nova-oidc-signing"
          }
        },
        "Code": {
          "ZipFile": "def lambda_handler(event, context):\n    return {}"
        }
      }
    },
    "NovaIdpAuthUrl": {
      "Type": "AWS::Lambda::Url",
      "Properties": {
        "TargetFunction": {
          "Ref": "NovaIdpAuthFunction"
        },
        "AuthType": "AWS_IAM"
      }
    },
    "NovaIdpTokenVendUrl": {
      "Type": "AWS::Lambda::Url",
      "Properties": {
        "TargetFunction": {
          "Ref": "NovaIdpTokenVendFunction"
        },
        "AuthType": "AWS_IAM"
      }
    },
    "NovaIdpJwksUrl": {
      "Type": "AWS::Lambda::Url",
      "Properties": {
        "TargetFunction": {
          "Ref": "NovaIdpJwksFunction"
        },
        "AuthType": "NONE"
      }
    }
  }
}
```